Pull request 1989: AG-24794-client-source

Squashed commit of the following:

commit 5cf83aafc8ddfea15b4f8e9b4061af021727f68c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Sep 1 14:58:03 2023 +0300

    client: imp code

commit 4325c8f610d1329ad38c800e20275668f1690f51
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Sep 1 14:38:33 2023 +0300

    all: add client source
This commit is contained in:
Stanislav Chzhen 2023-09-01 15:24:44 +03:00
parent 0182b9ec18
commit 905f9615a8
6 changed files with 93 additions and 86 deletions

View File

@ -77,8 +77,8 @@ func (n Neighbor) Clone() (clone Neighbor) {
} }
} }
// validatedHostname returns valid hostname. Otherwise returns empty string and // validatedHostname returns h if it's a valid hostname, or an empty string
// logs the error if hostname is not valid. // otherwise, logging the validation error.
func validatedHostname(h string) (host string) { func validatedHostname(h string) (host string) {
err := netutil.ValidateHostname(h) err := netutil.ValidateHostname(h)
if err != nil { if err != nil {

View File

@ -3,3 +3,52 @@
// //
// TODO(a.garipov): Expand. // TODO(a.garipov): Expand.
package client package client
import (
"encoding"
"fmt"
)
// Source represents the source from which the information about the client has
// been obtained.
type Source uint8
// Clients information sources. The order determines the priority.
const (
SourceNone Source = iota
SourceWHOIS
SourceARP
SourceRDNS
SourceDHCP
SourceHostsFile
SourcePersistent
)
// type check
var _ fmt.Stringer = Source(0)
// String returns a human-readable name of cs.
func (cs Source) String() (s string) {
switch cs {
case SourceWHOIS:
return "WHOIS"
case SourceARP:
return "ARP"
case SourceRDNS:
return "rDNS"
case SourceDHCP:
return "DHCP"
case SourceHostsFile:
return "etc/hosts"
default:
return ""
}
}
// type check
var _ encoding.TextMarshaler = Source(0)
// MarshalText implements encoding.TextMarshaler for the Source.
func (cs Source) MarshalText() (text []byte, err error) {
return []byte(cs.String()), nil
}

View File

@ -1,10 +1,10 @@
package home package home
import ( import (
"encoding"
"fmt" "fmt"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch" "github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/AdGuardHome/internal/whois"
@ -83,50 +83,6 @@ func (c *Client) setSafeSearch(
return nil return nil
} }
// clientSource represents the source from which the information about the
// client has been obtained.
type clientSource uint
// Clients information sources. The order determines the priority.
const (
ClientSourceNone clientSource = iota
ClientSourceWHOIS
ClientSourceARP
ClientSourceRDNS
ClientSourceDHCP
ClientSourceHostsFile
ClientSourcePersistent
)
// type check
var _ fmt.Stringer = clientSource(0)
// String returns a human-readable name of cs.
func (cs clientSource) String() (s string) {
switch cs {
case ClientSourceWHOIS:
return "WHOIS"
case ClientSourceARP:
return "ARP"
case ClientSourceRDNS:
return "rDNS"
case ClientSourceDHCP:
return "DHCP"
case ClientSourceHostsFile:
return "etc/hosts"
default:
return ""
}
}
// type check
var _ encoding.TextMarshaler = clientSource(0)
// MarshalText implements encoding.TextMarshaler for the clientSource.
func (cs clientSource) MarshalText() (text []byte, err error) {
return []byte(cs.String()), nil
}
// RuntimeClient is a client information about which has been obtained using the // RuntimeClient is a client information about which has been obtained using the
// source described in the Source field. // source described in the Source field.
type RuntimeClient struct { type RuntimeClient struct {
@ -138,5 +94,5 @@ type RuntimeClient struct {
// Source is the source from which the information about the client has // Source is the source from which the information about the client has
// been obtained. // been obtained.
Source clientSource Source client.Source
} }

View File

@ -307,15 +307,15 @@ func (clients *clientsContainer) periodicUpdate() {
} }
// clientSource checks if client with this IP address already exists and returns // clientSource checks if client with this IP address already exists and returns
// the source which updated it last. It returns [ClientSourceNone] if the // the source which updated it last. It returns [client.SourceNone] if the
// client doesn't exist. // client doesn't exist.
func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource) { func (clients *clientsContainer) clientSource(ip netip.Addr) (src client.Source) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
_, ok := clients.findLocked(ip.String()) _, ok := clients.findLocked(ip.String())
if ok { if ok {
return ClientSourcePersistent return client.SourcePersistent
} }
rc, ok := clients.ipToRC[ip] rc, ok := clients.ipToRC[ip]
@ -323,8 +323,8 @@ func (clients *clientsContainer) clientSource(ip netip.Addr) (src clientSource)
src = rc.Source src = rc.Source
} }
if src < ClientSourceDHCP && clients.dhcp.HostByIP(ip) != "" { if src < client.SourceDHCP && clients.dhcp.HostByIP(ip) != "" {
src = ClientSourceDHCP src = client.SourceDHCP
} }
return src return src
@ -533,7 +533,7 @@ func (clients *clientsContainer) runtimeClient(ip netip.Addr) (rc *RuntimeClient
// findRuntimeClient finds a runtime client by their IP. // findRuntimeClient finds a runtime client by their IP.
func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) { func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeClient, ok bool) {
if rc, ok = clients.runtimeClient(ip); ok && rc.Source > ClientSourceDHCP { if rc, ok = clients.runtimeClient(ip); ok && rc.Source > client.SourceDHCP {
return rc, ok return rc, ok
} }
@ -544,7 +544,7 @@ func (clients *clientsContainer) findRuntimeClient(ip netip.Addr) (rc *RuntimeCl
return &RuntimeClient{ return &RuntimeClient{
Host: host, Host: host,
Source: ClientSourceDHCP, Source: client.SourceDHCP,
WHOIS: &whois.Info{}, WHOIS: &whois.Info{},
}, true }, true
} }
@ -744,7 +744,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
// Create a RuntimeClient implicitly so that we don't do this check // Create a RuntimeClient implicitly so that we don't do this check
// again. // again.
rc = &RuntimeClient{ rc = &RuntimeClient{
Source: ClientSourceWHOIS, Source: client.SourceWHOIS,
} }
clients.ipToRC[ip] = rc clients.ipToRC[ip] = rc
@ -763,7 +763,7 @@ func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *whois.Info) {
func (clients *clientsContainer) addHost( func (clients *clientsContainer) addHost(
ip netip.Addr, ip netip.Addr,
host string, host string,
src clientSource, src client.Source,
) (ok bool) { ) (ok bool) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
@ -786,7 +786,7 @@ func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info
defer clients.lock.Unlock() defer clients.lock.Unlock()
if host != "" { if host != "" {
ok := clients.addHostLocked(ip, host, ClientSourceRDNS) ok := clients.addHostLocked(ip, host, client.SourceRDNS)
if !ok { if !ok {
log.Debug("clients: host for client %q already set with higher priority source", ip) log.Debug("clients: host for client %q already set with higher priority source", ip)
} }
@ -802,11 +802,11 @@ func (clients *clientsContainer) UpdateAddress(ip netip.Addr, host string, info
func (clients *clientsContainer) addHostLocked( func (clients *clientsContainer) addHostLocked(
ip netip.Addr, ip netip.Addr,
host string, host string,
src clientSource, src client.Source,
) (ok bool) { ) (ok bool) {
rc, ok := clients.ipToRC[ip] rc, ok := clients.ipToRC[ip]
if !ok { if !ok {
if src < ClientSourceDHCP { if src < client.SourceDHCP {
if clients.dhcp.HostByIP(ip) != "" { if clients.dhcp.HostByIP(ip) != "" {
return false return false
} }
@ -829,7 +829,7 @@ func (clients *clientsContainer) addHostLocked(
} }
// rmHostsBySrc removes all entries that match the specified source. // rmHostsBySrc removes all entries that match the specified source.
func (clients *clientsContainer) rmHostsBySrc(src clientSource) { func (clients *clientsContainer) rmHostsBySrc(src client.Source) {
n := 0 n := 0
for ip, rc := range clients.ipToRC { for ip, rc := range clients.ipToRC {
if rc.Source == src { if rc.Source == src {
@ -847,7 +847,7 @@ func (clients *clientsContainer) addFromHostsFile(hosts aghnet.Hosts) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
clients.rmHostsBySrc(ClientSourceHostsFile) clients.rmHostsBySrc(client.SourceHostsFile)
n := 0 n := 0
for addr, rec := range hosts { for addr, rec := range hosts {
@ -855,7 +855,7 @@ func (clients *clientsContainer) addFromHostsFile(hosts aghnet.Hosts) {
// hostname for the IP address. // hostname for the IP address.
// //
// TODO(e.burkov): Consider using all the names from all the records. // TODO(e.burkov): Consider using all the names from all the records.
clients.addHostLocked(addr, rec[0].Names[0], ClientSourceHostsFile) clients.addHostLocked(addr, rec[0].Names[0], client.SourceHostsFile)
n++ n++
} }
@ -883,11 +883,11 @@ func (clients *clientsContainer) addFromSystemARP() {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
clients.rmHostsBySrc(ClientSourceARP) clients.rmHostsBySrc(client.SourceARP)
added := 0 added := 0
for _, n := range ns { for _, n := range ns {
if clients.addHostLocked(n.IP, n.Name, ClientSourceARP) { if clients.addHostLocked(n.IP, n.Name, client.SourceARP) {
added++ added++
} }
} }

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd" "github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc" "github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
@ -99,9 +100,9 @@ func TestClients(t *testing.T) {
assert.Equal(t, "client2", c.Name) assert.Equal(t, "client2", c.Name)
assert.Equal(t, clients.clientSource(cliNoneIP), ClientSourceNone) assert.Equal(t, clients.clientSource(cliNoneIP), client.SourceNone)
assert.Equal(t, clients.clientSource(cli1IP), ClientSourcePersistent) assert.Equal(t, clients.clientSource(cli1IP), client.SourcePersistent)
assert.Equal(t, clients.clientSource(cli2IP), ClientSourcePersistent) assert.Equal(t, clients.clientSource(cli2IP), client.SourcePersistent)
}) })
t.Run("add_fail_name", func(t *testing.T) { t.Run("add_fail_name", func(t *testing.T) {
@ -148,8 +149,8 @@ func TestClients(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, clients.clientSource(cliOldIP), ClientSourceNone) assert.Equal(t, clients.clientSource(cliOldIP), client.SourceNone)
assert.Equal(t, clients.clientSource(cliNewIP), ClientSourcePersistent) assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent)
prev, ok = clients.list["client1"] prev, ok = clients.list["client1"]
require.True(t, ok) require.True(t, ok)
@ -181,7 +182,7 @@ func TestClients(t *testing.T) {
ok := clients.Del("client1-renamed") ok := clients.Del("client1-renamed")
require.True(t, ok) require.True(t, ok)
assert.Equal(t, clients.clientSource(netip.MustParseAddr("1.1.1.2")), ClientSourceNone) assert.Equal(t, clients.clientSource(netip.MustParseAddr("1.1.1.2")), client.SourceNone)
}) })
t.Run("del_fail", func(t *testing.T) { t.Run("del_fail", func(t *testing.T) {
@ -191,32 +192,32 @@ func TestClients(t *testing.T) {
t.Run("addhost_success", func(t *testing.T) { t.Run("addhost_success", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host", ClientSourceARP) ok := clients.addHost(ip, "host", client.SourceARP)
assert.True(t, ok) assert.True(t, ok)
ok = clients.addHost(ip, "host2", ClientSourceARP) ok = clients.addHost(ip, "host2", client.SourceARP)
assert.True(t, ok) assert.True(t, ok)
ok = clients.addHost(ip, "host3", ClientSourceHostsFile) ok = clients.addHost(ip, "host3", client.SourceHostsFile)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceHostsFile) assert.Equal(t, clients.clientSource(ip), client.SourceHostsFile)
}) })
t.Run("dhcp_replaces_arp", func(t *testing.T) { t.Run("dhcp_replaces_arp", func(t *testing.T) {
ip := netip.MustParseAddr("1.2.3.4") ip := netip.MustParseAddr("1.2.3.4")
ok := clients.addHost(ip, "from_arp", ClientSourceARP) ok := clients.addHost(ip, "from_arp", client.SourceARP)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceARP) assert.Equal(t, clients.clientSource(ip), client.SourceARP)
ok = clients.addHost(ip, "from_dhcp", ClientSourceDHCP) ok = clients.addHost(ip, "from_dhcp", client.SourceDHCP)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, clients.clientSource(ip), ClientSourceDHCP) assert.Equal(t, clients.clientSource(ip), client.SourceDHCP)
}) })
t.Run("addhost_fail", func(t *testing.T) { t.Run("addhost_fail", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host1", ClientSourceRDNS) ok := clients.addHost(ip, "host1", client.SourceRDNS)
assert.False(t, ok) assert.False(t, ok)
}) })
} }
@ -239,7 +240,7 @@ func TestClientsWHOIS(t *testing.T) {
t.Run("existing_auto-client", func(t *testing.T) { t.Run("existing_auto-client", func(t *testing.T) {
ip := netip.MustParseAddr("1.1.1.1") ip := netip.MustParseAddr("1.1.1.1")
ok := clients.addHost(ip, "host", ClientSourceRDNS) ok := clients.addHost(ip, "host", client.SourceRDNS)
assert.True(t, ok) assert.True(t, ok)
clients.setWHOISInfo(ip, whois) clients.setWHOISInfo(ip, whois)
@ -282,7 +283,7 @@ func TestClientsAddExisting(t *testing.T) {
assert.True(t, ok) assert.True(t, ok)
// Now add an auto-client with the same IP. // Now add an auto-client with the same IP.
ok = clients.addHost(ip, "test", ClientSourceRDNS) ok = clients.addHost(ip, "test", client.SourceRDNS)
assert.True(t, ok) assert.True(t, ok)
}) })

View File

@ -8,6 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/schedule" "github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/AdGuardHome/internal/whois"
@ -90,7 +91,7 @@ type runtimeClientJSON struct {
IP netip.Addr `json:"ip"` IP netip.Addr `json:"ip"`
Name string `json:"name"` Name string `json:"name"`
Source clientSource `json:"source"` Source client.Source `json:"source"`
} }
type clientListJSON struct { type clientListJSON struct {
@ -126,7 +127,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
for _, l := range clients.dhcp.Leases() { for _, l := range clients.dhcp.Leases() {
cj := runtimeClientJSON{ cj := runtimeClientJSON{
Name: l.Hostname, Name: l.Hostname,
Source: ClientSourceDHCP, Source: client.SourceDHCP,
IP: l.IP, IP: l.IP,
WHOIS: &whois.Info{}, WHOIS: &whois.Info{},
} }