mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-02-16 18:09:47 +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)
|
AddStaticLease(l *Lease) (err error)
|
||||||
// RemoveStaticLease - remove a static lease
|
// RemoveStaticLease - remove a static lease
|
||||||
RemoveStaticLease(l *Lease) (err error)
|
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 - copy disk configuration
|
||||||
WriteDiskConfig4(c *V4ServerConf)
|
WriteDiskConfig4(c *V4ServerConf)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -42,6 +43,10 @@ type Lease struct {
|
||||||
|
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
HWAddr net.HardwareAddr `json:"mac"`
|
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"`
|
IP net.IP `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +165,7 @@ type Interface interface {
|
||||||
|
|
||||||
Leases(flags GetLeasesFlags) (leases []*Lease)
|
Leases(flags GetLeasesFlags) (leases []*Lease)
|
||||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||||
FindMACbyIP(ip net.IP) (mac net.HardwareAddr)
|
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||||
|
|
||||||
WriteDiskConfig(c *ServerConfig)
|
WriteDiskConfig(c *ServerConfig)
|
||||||
}
|
}
|
||||||
|
@ -174,7 +179,7 @@ type MockInterface struct {
|
||||||
OnEnabled func() (ok bool)
|
OnEnabled func() (ok bool)
|
||||||
OnLeases func(flags GetLeasesFlags) (leases []*Lease)
|
OnLeases func(flags GetLeasesFlags) (leases []*Lease)
|
||||||
OnSetOnLeaseChanged func(f OnLeaseChangedT)
|
OnSetOnLeaseChanged func(f OnLeaseChangedT)
|
||||||
OnFindMACbyIP func(ip net.IP) (mac net.HardwareAddr)
|
OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr)
|
||||||
OnWriteDiskConfig func(c *ServerConfig)
|
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.
|
// SetOnLeaseChanged implements the Interface for *MockInterface.
|
||||||
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
|
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
|
||||||
|
|
||||||
// FindMACbyIP implements the Interface for *MockInterface.
|
// FindMACbyIP implements the [Interface] for *MockInterface.
|
||||||
func (s *MockInterface) FindMACbyIP(ip net.IP) (mac net.HardwareAddr) { return s.OnFindMACbyIP(ip) }
|
func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||||
|
return s.OnFindMACbyIP(ip)
|
||||||
|
}
|
||||||
|
|
||||||
// WriteDiskConfig implements the Interface for *MockInterface.
|
// WriteDiskConfig implements the Interface for *MockInterface.
|
||||||
func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) }
|
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)...)
|
return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
// FindMACbyIP returns a MAC address by the IP address of its lease, if there is
|
||||||
func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
// one.
|
||||||
if ip.To4() != nil {
|
func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||||
|
if ip.Is4() {
|
||||||
return s.srv4.FindMACbyIP(ip)
|
return s.srv4.FindMACbyIP(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.srv6.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
|
// '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{}
|
type winServer struct{}
|
||||||
|
|
||||||
|
@ -16,7 +19,7 @@ func (winServer) GetLeases(_ GetLeasesFlags) (leases []*Lease) { return nil }
|
||||||
func (winServer) getLeasesRef() []*Lease { return nil }
|
func (winServer) getLeasesRef() []*Lease { return nil }
|
||||||
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
func (winServer) AddStaticLease(_ *Lease) (err error) { return nil }
|
||||||
func (winServer) RemoveStaticLease(_ *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) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||||
func (winServer) Start() (err error) { return nil }
|
func (winServer) Start() (err error) { return nil }
|
||||||
|
|
|
@ -200,20 +200,20 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||||
return leases
|
return leases
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
// FindMACbyIP implements the [Interface] for *v4Server.
|
||||||
func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
defer s.leasesLock.Unlock()
|
defer s.leasesLock.Unlock()
|
||||||
|
|
||||||
ip4 := ip.To4()
|
if !ip.Is4() {
|
||||||
if ip4 == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
netIP := ip.AsSlice()
|
||||||
for _, l := range s.leases {
|
for _, l := range s.leases {
|
||||||
if l.IP.Equal(ip4) {
|
if l.IP.Equal(netIP) {
|
||||||
if l.Expiry.After(now) || l.IsStatic() {
|
if l.Expiry.After(now) || l.IsStatic() {
|
||||||
return l.HWAddr
|
return l.HWAddr
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -107,21 +108,26 @@ func (s *v6Server) getLeasesRef() []*Lease {
|
||||||
return s.leases
|
return s.leases
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
// FindMACbyIP implements the [Interface] for *v6Server.
|
||||||
func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
func (s *v6Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||||
now := time.Now().Unix()
|
now := time.Now()
|
||||||
|
|
||||||
s.leasesLock.Lock()
|
s.leasesLock.Lock()
|
||||||
defer s.leasesLock.Unlock()
|
defer s.leasesLock.Unlock()
|
||||||
|
|
||||||
|
if !ip.Is6() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
netIP := ip.AsSlice()
|
||||||
for _, l := range s.leases {
|
for _, l := range s.leases {
|
||||||
if l.IP.Equal(ip) {
|
if l.IP.Equal(netIP) {
|
||||||
unix := l.Expiry.Unix()
|
if l.Expiry.After(now) || l.IsStatic() {
|
||||||
if unix > now || unix == leaseExpireStatic {
|
|
||||||
return l.HWAddr
|
return l.HWAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -1009,7 +1010,7 @@ var testDHCP = &dhcpd.MockInterface{
|
||||||
}}
|
}}
|
||||||
},
|
},
|
||||||
OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {},
|
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") },
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
@ -25,122 +24,16 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const clientsUpdatePeriod = 10 * time.Minute
|
// clientsContainer is the storage of all runtime and persistent clients.
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientsContainer struct {
|
type clientsContainer struct {
|
||||||
// TODO(a.garipov): Perhaps use a number of separate indices for
|
// TODO(a.garipov): Perhaps use a number of separate indices for different
|
||||||
// different types (string, netip.Addr, and so on).
|
// types (string, netip.Addr, and so on).
|
||||||
list map[string]*Client // name -> client
|
list map[string]*Client // name -> client
|
||||||
idIndex map[string]*Client // ID -> client
|
idIndex map[string]*Client // ID -> client
|
||||||
|
|
||||||
// ipToRC is the IP address to *RuntimeClient map.
|
// ipToRC is the IP address to *RuntimeClient map.
|
||||||
ipToRC map[netip.Addr]*RuntimeClient
|
ipToRC map[netip.Addr]*RuntimeClient
|
||||||
|
|
||||||
lock sync.Mutex
|
|
||||||
|
|
||||||
allTags *stringutil.Set
|
allTags *stringutil.Set
|
||||||
|
|
||||||
// dhcpServer is used for looking up clients IP addresses by MAC addresses
|
// 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 stores the neighbors retrieved from ARP.
|
||||||
arpdb aghnet.ARPDB
|
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
|
// 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() {
|
func (clients *clientsContainer) Start() {
|
||||||
if !clients.testing {
|
if clients.testing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !webHandlersRegistered {
|
if !webHandlersRegistered {
|
||||||
webHandlersRegistered = true
|
webHandlersRegistered = true
|
||||||
clients.registerWebHandlers()
|
clients.registerWebHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
go clients.periodicUpdate()
|
go clients.periodicUpdate()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Reload reloads runtime clients.
|
// reloadARP reloads runtime clients from ARP, if configured.
|
||||||
func (clients *clientsContainer) Reload() {
|
func (clients *clientsContainer) reloadARP() {
|
||||||
if clients.arpdb != nil {
|
if clients.arpdb != nil {
|
||||||
clients.addFromSystemARP()
|
clients.addFromSystemARP()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientObject is the YAML representation of a persistent client.
|
||||||
type clientObject struct {
|
type clientObject struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
|
|
||||||
|
@ -317,12 +229,15 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
|
||||||
return objs
|
return objs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arpClientsUpdatePeriod defines how often ARP clients are updated.
|
||||||
|
const arpClientsUpdatePeriod = 10 * time.Minute
|
||||||
|
|
||||||
func (clients *clientsContainer) periodicUpdate() {
|
func (clients *clientsContainer) periodicUpdate() {
|
||||||
defer log.OnPanic("clients container")
|
defer log.OnPanic("clients container")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
clients.Reload()
|
clients.reloadARP()
|
||||||
time.Sleep(clientsUpdatePeriod)
|
time.Sleep(arpClientsUpdatePeriod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +400,8 @@ func (clients *clientsContainer) findUpstreams(
|
||||||
return conf, nil
|
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) {
|
func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||||
c, ok = clients.idIndex[id]
|
c, ok = clients.idIndex[id]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -499,13 +415,13 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||||
|
|
||||||
for _, c = range clients.list {
|
for _, c = range clients.list {
|
||||||
for _, id := range c.IDs {
|
for _, id := range c.IDs {
|
||||||
var n netip.Prefix
|
var subnet netip.Prefix
|
||||||
n, err = netip.ParsePrefix(id)
|
subnet, err = netip.ParsePrefix(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Contains(ip) {
|
if subnet.Contains(ip) {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,20 +431,25 @@ func (clients *clientsContainer) findLocked(id string) (c *Client, ok bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
macFound := clients.dhcpServer.FindMACbyIP(ip.AsSlice())
|
return clients.findDHCP(ip)
|
||||||
if macFound == nil {
|
}
|
||||||
|
|
||||||
|
// 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
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c = range clients.list {
|
for _, c = range clients.list {
|
||||||
for _, id := range c.IDs {
|
for _, id := range c.IDs {
|
||||||
var mac net.HardwareAddr
|
mac, err := net.ParseMAC(id)
|
||||||
mac, err = net.ParseMAC(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(mac, macFound) {
|
if bytes.Equal(mac, foundMAC) {
|
||||||
return c, true
|
return c, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,24 +486,13 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, id := range c.IDs {
|
for i, id := range c.IDs {
|
||||||
// Normalize structured data.
|
var norm string
|
||||||
var (
|
norm, err = normalizeClientIdentifier(id)
|
||||||
ip netip.Addr
|
if err != nil {
|
||||||
n netip.Prefix
|
return fmt.Errorf("client at index %d: %w", i, err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.IDs[i] = norm
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range c.Tags {
|
for _, t := range c.Tags {
|
||||||
|
@ -601,6 +511,35 @@ func (clients *clientsContainer) check(c *Client) (err error) {
|
||||||
return nil
|
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
|
// Add adds a new client object. ok is false if such client already exists or
|
||||||
// if an error occurred.
|
// if an error occurred.
|
||||||
func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
|
func (clients *clientsContainer) Add(c *Client) (ok bool, err error) {
|
||||||
|
@ -666,21 +605,6 @@ func (clients *clientsContainer) Del(name string) (ok bool) {
|
||||||
return true
|
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.
|
// Update updates a client by its name.
|
||||||
func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||||
err = clients.check(c)
|
err = clients.check(c)
|
||||||
|
@ -704,22 +628,11 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second, check the IP index.
|
// Second, update the ID index.
|
||||||
if !equalStringSlices(prev.IDs, c.IDs) {
|
err = clients.updateIDIndex(prev, c.IDs)
|
||||||
for _, id := range c.IDs {
|
if err != nil {
|
||||||
c2, ok2 := clients.idIndex[id]
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
if ok2 && c2 != prev {
|
return err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update name index.
|
// Update name index.
|
||||||
|
@ -739,6 +652,32 @@ func (clients *clientsContainer) Update(name string, c *Client) (err error) {
|
||||||
return nil
|
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.
|
// setWHOISInfo sets the WHOIS information for a client.
|
||||||
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
|
func (clients *clientsContainer) setWHOISInfo(ip netip.Addr, wi *RuntimeClientWHOISInfo) {
|
||||||
clients.lock.Lock()
|
clients.lock.Lock()
|
||||||
|
|
|
@ -75,11 +75,21 @@ type osConfig struct {
|
||||||
|
|
||||||
type clientsConfig struct {
|
type clientsConfig struct {
|
||||||
// Sources defines the set of sources to fetch the runtime clients from.
|
// 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 are the configured clients.
|
||||||
Persistent []*clientObject `yaml:"persistent"`
|
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
|
// configuration is loaded from YAML
|
||||||
// field ordering is important -- yaml fields will mirror ordering from here
|
// field ordering is important -- yaml fields will mirror ordering from here
|
||||||
type configuration struct {
|
type configuration struct {
|
||||||
|
@ -336,7 +346,7 @@ var config = &configuration{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Clients: &clientsConfig{
|
Clients: &clientsConfig{
|
||||||
Sources: &clientSourcesConf{
|
Sources: &clientSourcesConfig{
|
||||||
WHOIS: true,
|
WHOIS: true,
|
||||||
ARP: true,
|
ARP: true,
|
||||||
RDNS: true,
|
RDNS: true,
|
||||||
|
|
|
@ -125,7 +125,7 @@ func Main(clientBuildFS fs.FS) {
|
||||||
log.Info("Received signal %q", sig)
|
log.Info("Received signal %q", sig)
|
||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
Context.clients.Reload()
|
Context.clients.reloadARP()
|
||||||
Context.tls.reload()
|
Context.tls.reload()
|
||||||
default:
|
default:
|
||||||
cleanup(context.Background())
|
cleanup(context.Background())
|
||||||
|
|
|
@ -792,7 +792,7 @@ func upgradeSchema13to14(diskConf yobj) (err error) {
|
||||||
|
|
||||||
diskConf["clients"] = yobj{
|
diskConf["clients"] = yobj{
|
||||||
"persistent": clientsVal,
|
"persistent": clientsVal,
|
||||||
"runtime_sources": &clientSourcesConf{
|
"runtime_sources": &clientSourcesConfig{
|
||||||
WHOIS: true,
|
WHOIS: true,
|
||||||
ARP: true,
|
ARP: true,
|
||||||
RDNS: rdnsSrc,
|
RDNS: rdnsSrc,
|
||||||
|
|
|
@ -579,7 +579,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||||
// The clients field will be added anyway.
|
// The clients field will be added anyway.
|
||||||
"clients": yobj{
|
"clients": yobj{
|
||||||
"persistent": yarr{},
|
"persistent": yarr{},
|
||||||
"runtime_sources": &clientSourcesConf{
|
"runtime_sources": &clientSourcesConfig{
|
||||||
WHOIS: true,
|
WHOIS: true,
|
||||||
ARP: true,
|
ARP: true,
|
||||||
RDNS: false,
|
RDNS: false,
|
||||||
|
@ -597,7 +597,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||||
"schema_version": newSchemaVer,
|
"schema_version": newSchemaVer,
|
||||||
"clients": yobj{
|
"clients": yobj{
|
||||||
"persistent": []*clientObject{testClient},
|
"persistent": []*clientObject{testClient},
|
||||||
"runtime_sources": &clientSourcesConf{
|
"runtime_sources": &clientSourcesConfig{
|
||||||
WHOIS: true,
|
WHOIS: true,
|
||||||
ARP: true,
|
ARP: true,
|
||||||
RDNS: false,
|
RDNS: false,
|
||||||
|
@ -618,7 +618,7 @@ func TestUpgradeSchema13to14(t *testing.T) {
|
||||||
"schema_version": newSchemaVer,
|
"schema_version": newSchemaVer,
|
||||||
"clients": yobj{
|
"clients": yobj{
|
||||||
"persistent": []*clientObject{testClient},
|
"persistent": []*clientObject{testClient},
|
||||||
"runtime_sources": &clientSourcesConf{
|
"runtime_sources": &clientSourcesConfig{
|
||||||
WHOIS: true,
|
WHOIS: true,
|
||||||
ARP: true,
|
ARP: true,
|
||||||
RDNS: true,
|
RDNS: true,
|
||||||
|
|
Loading…
Add table
Reference in a new issue