mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-21 20:45:33 +03:00
Pull request 1755: imp-home-gocyclo
Updates #2646. Squashed commit of the following: commit 9db0df757ef4e61a066264948436d465a720e2ec Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Mar 1 13:24:12 2023 +0300 home: imp docs commit d1e419962d7b0fa2347e141f600c89854bf7ebb2 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Mar 1 13:19:42 2023 +0300 home: imp docs, names commit 176368649729e6949ef2f7325aafa336f4725d88 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Tue Feb 28 20:45:50 2023 +0300 all: imp cyclo, docs; use netip.Addr
This commit is contained in:
parent
bb226434f8
commit
f13dc59bfa
12 changed files with 291 additions and 219 deletions
|
@ -45,8 +45,10 @@ type DHCPServer interface {
|
|||
AddStaticLease(l *Lease) (err error)
|
||||
// RemoveStaticLease - remove a static lease
|
||||
RemoveStaticLease(l *Lease) (err error)
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
FindMACbyIP(ip net.IP) net.HardwareAddr
|
||||
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if
|
||||
// there is one.
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// WriteDiskConfig4 - copy disk configuration
|
||||
WriteDiskConfig4(c *V4ServerConf)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
|
@ -42,6 +43,10 @@ type Lease struct {
|
|||
|
||||
Hostname string `json:"hostname"`
|
||||
HWAddr net.HardwareAddr `json:"mac"`
|
||||
|
||||
// IP is the IP address leased to the client.
|
||||
//
|
||||
// TODO(a.garipov): Migrate leases.db and use netip.Addr.
|
||||
IP net.IP `json:"ip"`
|
||||
}
|
||||
|
||||
|
@ -160,7 +165,7 @@ type Interface interface {
|
|||
|
||||
Leases(flags GetLeasesFlags) (leases []*Lease)
|
||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||
FindMACbyIP(ip net.IP) (mac net.HardwareAddr)
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
WriteDiskConfig(c *ServerConfig)
|
||||
}
|
||||
|
@ -174,7 +179,7 @@ type MockInterface struct {
|
|||
OnEnabled func() (ok bool)
|
||||
OnLeases func(flags GetLeasesFlags) (leases []*Lease)
|
||||
OnSetOnLeaseChanged func(f OnLeaseChangedT)
|
||||
OnFindMACbyIP func(ip net.IP) (mac net.HardwareAddr)
|
||||
OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr)
|
||||
OnWriteDiskConfig func(c *ServerConfig)
|
||||
}
|
||||
|
||||
|
@ -195,8 +200,10 @@ func (s *MockInterface) Leases(flags GetLeasesFlags) (ls []*Lease) { return s.On
|
|||
// SetOnLeaseChanged implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
|
||||
|
||||
// FindMACbyIP implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) FindMACbyIP(ip net.IP) (mac net.HardwareAddr) { return s.OnFindMACbyIP(ip) }
|
||||
// FindMACbyIP implements the [Interface] for *MockInterface.
|
||||
func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
return s.OnFindMACbyIP(ip)
|
||||
}
|
||||
|
||||
// WriteDiskConfig implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) }
|
||||
|
@ -375,11 +382,13 @@ func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) {
|
|||
return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
if ip.To4() != nil {
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if there is
|
||||
// one.
|
||||
func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
if ip.Is4() {
|
||||
return s.srv4.FindMACbyIP(ip)
|
||||
}
|
||||
|
||||
return s.srv6.FindMACbyIP(ip)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@ package dhcpd
|
|||
|
||||
// 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type winServer struct{}
|
||||
|
||||
|
@ -16,7 +19,7 @@ func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
|||
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) RemoveStaticLease(_ *Lease) (err error) { return nil }
|
||||
func (winServer) FindMACbyIP(_ net.IP) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) FindMACbyIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
|
||||
func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
|
|
|
@ -200,20 +200,20 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
|||
return leases
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
// FindMACbyIP implements the [Interface] for *v4Server.
|
||||
func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
now := time.Now()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
ip4 := ip.To4()
|
||||
if ip4 == nil {
|
||||
if !ip.Is4() {
|
||||
return nil
|
||||
}
|
||||
|
||||
netIP := ip.AsSlice()
|
||||
for _, l := range s.leases {
|
||||
if l.IP.Equal(ip4) {
|
||||
if l.IP.Equal(netIP) {
|
||||
if l.Expiry.After(now) || l.IsStatic() {
|
||||
return l.HWAddr
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -107,21 +108,26 @@ func (s *v6Server) getLeasesRef() []*Lease {
|
|||
return s.leases
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
now := time.Now().Unix()
|
||||
// FindMACbyIP implements the [Interface] for *v6Server.
|
||||
func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
now := time.Now()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if !ip.Is6() {
|
||||
return nil
|
||||
}
|
||||
|
||||
netIP := ip.AsSlice()
|
||||
for _, l := range s.leases {
|
||||
if l.IP.Equal(ip) {
|
||||
unix := l.Expiry.Unix()
|
||||
if unix > now || unix == leaseExpireStatic {
|
||||
if l.IP.Equal(netIP) {
|
||||
if l.Expiry.After(now) || l.IsStatic() {
|
||||
return l.HWAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
@ -1009,7 +1010,7 @@ var testDHCP = &dhcpd.MockInterface{
|
|||
}}
|
||||
},
|
||||
OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {},
|
||||
OnFindMACbyIP: func(ip net.IP) (mac net.HardwareAddr) { panic("not implemented") },
|
||||
OnFindMACbyIP: func(ip netip.Addr) (mac net.HardwareAddr) { panic("not implemented") },
|
||||
OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") },
|
||||
}
|
||||
|
||||
|
|
102
internal/home/client.go
Normal file
102
internal/home/client.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package home
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
)
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
UseOwnBlockedServices bool
|
||||
}
|
||||
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Client) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
err = c.upstreamConfig.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// source described in the Source field.
|
||||
type RuntimeClient struct {
|
||||
WHOISInfo *RuntimeClientWHOISInfo
|
||||
Host string
|
||||
Source clientSource
|
||||
}
|
||||
|
||||
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client.
|
||||
type RuntimeClientWHOISInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Orgname string `json:"orgname,omitempty"`
|
||||
}
|
|
@ -2,7 +2,6 @@ package home
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
@ -25,122 +24,16 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const clientsUpdatePeriod = 10 * time.Minute
|
||||
|
||||
var webHandlersRegistered = false
|
||||
|
||||
// Client contains information about persistent clients.
|
||||
type Client struct {
|
||||
// upstreamConfig is the custom upstream config for this client. If
|
||||
// it's nil, it has not been initialized yet. If it's non-nil and
|
||||
// empty, there are no valid upstreams. If it's non-nil and non-empty,
|
||||
// these upstream must be used.
|
||||
upstreamConfig *proxy.UpstreamConfig
|
||||
|
||||
Name string
|
||||
|
||||
IDs []string
|
||||
Tags []string
|
||||
BlockedServices []string
|
||||
Upstreams []string
|
||||
|
||||
UseOwnSettings bool
|
||||
FilteringEnabled bool
|
||||
SafeSearchEnabled bool
|
||||
SafeBrowsingEnabled bool
|
||||
ParentalEnabled bool
|
||||
UseOwnBlockedServices bool
|
||||
}
|
||||
|
||||
// closeUpstreams closes the client-specific upstream config of c if any.
|
||||
func (c *Client) closeUpstreams() (err error) {
|
||||
if c.upstreamConfig != nil {
|
||||
err = c.upstreamConfig.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing upstreams of client %q: %w", c.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// clientSourceConf is used to configure where the runtime clients will be
|
||||
// obtained from.
|
||||
type clientSourcesConf struct {
|
||||
WHOIS bool `yaml:"whois"`
|
||||
ARP bool `yaml:"arp"`
|
||||
RDNS bool `yaml:"rdns"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
HostsFile bool `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// RuntimeClient information
|
||||
type RuntimeClient struct {
|
||||
WHOISInfo *RuntimeClientWHOISInfo
|
||||
Host string
|
||||
Source clientSource
|
||||
}
|
||||
|
||||
// RuntimeClientWHOISInfo is the filtered WHOIS data for a runtime client.
|
||||
type RuntimeClientWHOISInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Orgname string `json:"orgname,omitempty"`
|
||||
}
|
||||
|
||||
// clientsContainer is the storage of all runtime and persistent clients.
|
||||
type clientsContainer struct {
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for
|
||||
// different types (string, netip.Addr, and so on).
|
||||
// TODO(a.garipov): Perhaps use a number of separate indices for different
|
||||
// types (string, netip.Addr, and so on).
|
||||
list map[string]*Client // name -> client
|
||||
idIndex map[string]*Client // ID -> client
|
||||
|
||||
// ipToRC is the IP address to *RuntimeClient map.
|
||||
ipToRC map[netip.Addr]*RuntimeClient
|
||||
|
||||
lock sync.Mutex
|
||||
|
||||
allTags *stringutil.Set
|
||||
|
||||
// dhcpServer is used for looking up clients IP addresses by MAC addresses
|
||||
|
@ -156,7 +49,16 @@ type clientsContainer struct {
|
|||
// arpdb stores the neighbors retrieved from ARP.
|
||||
arpdb aghnet.ARPDB
|
||||
|
||||
testing bool // if TRUE, this object is used for internal tests
|
||||
// lock protects all fields.
|
||||
//
|
||||
// TODO(a.garipov): Use a pointer and describe which fields are protected in
|
||||
// more detail.
|
||||
lock sync.Mutex
|
||||
|
||||
// testing is a flag that disables some features for internal tests.
|
||||
//
|
||||
// TODO(a.garipov): Awful. Remove.
|
||||
testing bool
|
||||
}
|
||||
|
||||
// Init initializes clients container
|
||||
|
@ -202,24 +104,34 @@ func (clients *clientsContainer) handleHostsUpdates() {
|
|||
}
|
||||
}
|
||||
|
||||
// Start - start the module
|
||||
// webHandlersRegistered prevents a [clientsContainer] from regisering its web
|
||||
// handlers more than once.
|
||||
//
|
||||
// TODO(a.garipov): Refactor HTTP handler registration logic.
|
||||
var webHandlersRegistered = false
|
||||
|
||||
// Start starts the clients container.
|
||||
func (clients *clientsContainer) Start() {
|
||||
if !clients.testing {
|
||||
if clients.testing {
|
||||
return
|
||||
}
|
||||
|
||||
if !webHandlersRegistered {
|
||||
webHandlersRegistered = true
|
||||
clients.registerWebHandlers()
|
||||
}
|
||||
|
||||
go clients.periodicUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// Reload reloads runtime clients.
|
||||
func (clients *clientsContainer) Reload() {
|
||||
// reloadARP reloads runtime clients from ARP, if configured.
|
||||
func (clients *clientsContainer) reloadARP() {
|
||||
if clients.arpdb != nil {
|
||||
clients.addFromSystemARP()
|
||||
}
|
||||
}
|
||||
|
||||
// clientObject is the YAML representation of a persistent client.
|
||||
type clientObject struct {
|
||||
Name string `yaml:"name"`
|
||||
|
||||
|
@ -317,12 +229,15 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
|||
return objs
|
||||
}
|
||||
|
||||
// arpClientsUpdatePeriod defines how often ARP clients are updated.
|
||||
const arpClientsUpdatePeriod = 10 * time.Minute
|
||||
|
||||
func (clients *clientsContainer) periodicUpdate() {
|
||||
defer log.OnPanic("clients container")
|
||||
|
||||
for {
|
||||
clients.Reload()
|
||||
time.Sleep(clientsUpdatePeriod)
|
||||
clients.reloadARP()
|
||||
time.Sleep(arpClientsUpdatePeriod)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -485,7 +400,8 @@ func (clients *clientsContainer) findUpstreams(
|
|||
return conf, nil
|
||||
}
|
||||
|
||||
// findLocked searches for a client by its ID. For internal use only.
|
||||
// findLocked searches for a client by its ID. clients.lock is expected to be
|
||||
// locked.
|
||||
func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||
c, ok = clients.idIndex[id]
|
||||
if ok {
|
||||
|
@ -499,13 +415,13 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
|||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
var n netip.Prefix
|
||||
n, err = netip.ParsePrefix(id)
|
||||
var subnet netip.Prefix
|
||||
subnet, err = netip.ParsePrefix(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if n.Contains(ip) {
|
||||
if subnet.Contains(ip) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
|
@ -515,20 +431,25 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
|
||||
if macFound == nil {
|
||||
return clients.findDHCP(ip)
|
||||
}
|
||||
|
||||
// findDHCP searches for a client by its MAC, if the DHCP server is active and
|
||||
// there is such client. clients.lock is expected to be locked.
|
||||
func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *Client, ok bool) {
|
||||
foundMAC := clients.dhcpServer.FindMACbyIP(ip)
|
||||
if foundMAC == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
var mac net.HardwareAddr
|
||||
mac, err = net.ParseMAC(id)
|
||||
mac, err := net.ParseMAC(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(mac, macFound) {
|
||||
if bytes.Equal(mac, foundMAC) {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
|
@ -565,24 +486,13 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
|||
}
|
||||
|
||||
for i, id := range c.IDs {
|
||||
// Normalize structured data.
|
||||
var (
|
||||
ip netip.Addr
|
||||
n netip.Prefix
|
||||
mac net.HardwareAddr
|
||||
)
|
||||
|
||||
if ip, err = netip.ParseAddr(id); err == nil {
|
||||
c.IDs[i] = ip.String()
|
||||
} else if n, err = netip.ParsePrefix(id); err == nil {
|
||||
c.IDs[i] = n.String()
|
||||
} else if mac, err = net.ParseMAC(id); err == nil {
|
||||
c.IDs[i] = mac.String()
|
||||
} else if err = dnsforward.ValidateClientID(id); err == nil {
|
||||
c.IDs[i] = strings.ToLower(id)
|
||||
} else {
|
||||
return fmt.Errorf("invalid clientid at index %d: %q", i, id)
|
||||
var norm string
|
||||
norm, err = normalizeClientIdentifier(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
c.IDs[i] = norm
|
||||
}
|
||||
|
||||
for _, t := range c.Tags {
|
||||
|
@ -601,6 +511,35 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// normalizeClientIdentifier returns a normalized version of idStr. If idStr
|
||||
// cannot be normalized, it returns an error.
|
||||
func normalizeClientIdentifier(idStr string) (norm string, err error) {
|
||||
if idStr == "" {
|
||||
return "", errors.Error("clientid is empty")
|
||||
}
|
||||
|
||||
var ip netip.Addr
|
||||
if ip, err = netip.ParseAddr(idStr); err == nil {
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
var subnet netip.Prefix
|
||||
if subnet, err = netip.ParsePrefix(idStr); err == nil {
|
||||
return subnet.String(), nil
|
||||
}
|
||||
|
||||
var mac net.HardwareAddr
|
||||
if mac, err = net.ParseMAC(idStr); err == nil {
|
||||
return mac.String(), nil
|
||||
}
|
||||
|
||||
if err = dnsforward.ValidateClientID(idStr); err == nil {
|
||||
return strings.ToLower(idStr), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("bad client identifier %q", idStr)
|
||||
}
|
||||
|
||||
// Add adds a new client object. ok is false if such client already exists or
|
||||
// if an error occurred.
|
||||
func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
|
||||
|
@ -666,21 +605,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
|
|||
return true
|
||||
}
|
||||
|
||||
// equalStringSlices returns true if the slices are equal.
|
||||
func equalStringSlices(a, b []string) (ok bool) {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Update updates a client by its name.
|
||||
func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||
err = clients.check(c)
|
||||
|
@ -704,22 +628,11 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Second, check the IP index.
|
||||
if !equalStringSlices(prev.IDs, c.IDs) {
|
||||
for _, id := range c.IDs {
|
||||
c2, ok2 := clients.idIndex[id]
|
||||
if ok2 && c2 != prev {
|
||||
return fmt.Errorf("another client uses the same id (%q): %q", id, c2.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update ID index.
|
||||
for _, id := range prev.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
for _, id := range c.IDs {
|
||||
clients.idIndex[id] = prev
|
||||
}
|
||||
// Second, update the ID index.
|
||||
err = clients.updateIDIndex(prev, c.IDs)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// Update name index.
|
||||
|
@ -739,6 +652,32 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// updateIDIndex updates the ID index data for cli using the information from
|
||||
// newIDs.
|
||||
func (clients *clientsContainer) updateIDIndex(cli *Client, newIDs []string) (err error) {
|
||||
if slices.Equal(cli.IDs, newIDs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, id := range newIDs {
|
||||
existing, ok := clients.idIndex[id]
|
||||
if ok && existing != cli {
|
||||
return fmt.Errorf("id %q is used by client with name %q", id, existing.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the IDs in the index.
|
||||
for _, id := range cli.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
|
||||
for _, id := range newIDs {
|
||||
clients.idIndex[id] = cli
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setWHOISInfo sets the WHOIS information for a client.
|
||||
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
|
||||
clients.lock.Lock()
|
||||
|
|
|
@ -75,11 +75,21 @@ type osConfig struct {
|
|||
|
||||
type clientsConfig struct {
|
||||
// Sources defines the set of sources to fetch the runtime clients from.
|
||||
Sources *clientSourcesConf `yaml:"runtime_sources"`
|
||||
Sources *clientSourcesConfig `yaml:"runtime_sources"`
|
||||
// Persistent are the configured clients.
|
||||
Persistent []*clientObject `yaml:"persistent"`
|
||||
}
|
||||
|
||||
// clientSourceConfig is used to configure where the runtime clients will be
|
||||
// obtained from.
|
||||
type clientSourcesConfig struct {
|
||||
WHOIS bool `yaml:"whois"`
|
||||
ARP bool `yaml:"arp"`
|
||||
RDNS bool `yaml:"rdns"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
HostsFile bool `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// configuration is loaded from YAML
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type configuration struct {
|
||||
|
@ -336,7 +346,7 @@ var config = &configuration{
|
|||
},
|
||||
},
|
||||
Clients: &clientsConfig{
|
||||
Sources: &clientSourcesConf{
|
||||
Sources: &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: true,
|
||||
|
|
|
@ -125,7 +125,7 @@ func Main(clientBuildFS fs.FS) {
|
|||
log.Info("Received signal %q", sig)
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
Context.clients.Reload()
|
||||
Context.clients.reloadARP()
|
||||
Context.tls.reload()
|
||||
default:
|
||||
cleanup(context.Background())
|
||||
|
|
|
@ -792,7 +792,7 @@ func upgradeSchema13to14(diskConf yobj) (err error) {
|
|||
|
||||
diskConf["clients"] = yobj{
|
||||
"persistent": clientsVal,
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: rdnsSrc,
|
||||
|
|
|
@ -579,7 +579,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
|||
// The clients field will be added anyway.
|
||||
"clients": yobj{
|
||||
"persistent": yarr{},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: false,
|
||||
|
@ -597,7 +597,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
|||
"schema_version": newSchemaVer,
|
||||
"clients": yobj{
|
||||
"persistent": []*clientObject{testClient},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: false,
|
||||
|
@ -618,7 +618,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
|||
"schema_version": newSchemaVer,
|
||||
"clients": yobj{
|
||||
"persistent": []*clientObject{testClient},
|
||||
"runtime_sources": &clientSourcesConf{
|
||||
"runtime_sources": &clientSourcesConfig{
|
||||
WHOIS: true,
|
||||
ARP: true,
|
||||
RDNS: true,
|
||||
|
|
Loading…
Reference in a new issue