mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-28 18:08:51 +03:00
c1ee2c7e5e
Updates #6312. Squashed commit of the following: commit bd9146ee161a67fa41763070f985e1e73b85823b Merge: 58d2fd98d856cc40cf
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Apr 24 18:09:19 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 58d2fd98d3e82c84638d58dd4d74d13a9a8fbca6 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Apr 24 18:00:56 2024 +0300 client: imp naming commit 922a14b036d829c2775feb7bb3e6beb6aa49692e Merge: 6f4d58fe160f48e2d0
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Apr 24 14:29:00 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 6f4d58fe1c42504e8345bff24dbb3f523e8c5f85 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Apr 24 14:27:55 2024 +0300 client: imp docs commit fa292eee828cd6f27f62b782675aa1f998e44518 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Apr 22 19:20:28 2024 +0300 client: fix typo commit 599414be0ccd3f9deb044e022a8ac0006c96b467 Merge: 502571756762ef4a6d
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Apr 22 18:42:06 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 502571756400a00445086b5ba412e03fca65e39f Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Apr 22 18:39:22 2024 +0300 all: imp code; add tests commit 155b2fef500a0d835f49957d9f30b0870712f6f2 Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Apr 16 19:56:00 2024 +0300 all: upd chlog; imp code commit 7a4426c5d0a511cd3865884c00328b8c130746bf Merge: e9c1cbb8548c6242a7
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Apr 16 19:52:00 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit e9c1cbb85e4afa173969d5bedfaaaf92716b7fad Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Apr 10 16:23:07 2024 +0300 client: client ipv6 zone
271 lines
6.1 KiB
Go
271 lines
6.1 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
|
)
|
|
|
|
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
|
|
type macKey any
|
|
|
|
// macToKey converts mac into key of type macKey, which is used as the key of
|
|
// the [clientIndex.macToUID]. mac must be valid MAC address.
|
|
func macToKey(mac net.HardwareAddr) (key macKey) {
|
|
switch len(mac) {
|
|
case 6:
|
|
return [6]byte(mac)
|
|
case 8:
|
|
return [8]byte(mac)
|
|
case 20:
|
|
return [20]byte(mac)
|
|
default:
|
|
panic(fmt.Errorf("invalid mac address %#v", mac))
|
|
}
|
|
}
|
|
|
|
// Index stores all information about persistent clients.
|
|
type Index struct {
|
|
// clientIDToUID maps client ID to UID.
|
|
clientIDToUID map[string]UID
|
|
|
|
// ipToUID maps IP address to UID.
|
|
ipToUID map[netip.Addr]UID
|
|
|
|
// macToUID maps MAC address to UID.
|
|
macToUID map[macKey]UID
|
|
|
|
// uidToClient maps UID to the persistent client.
|
|
uidToClient map[UID]*Persistent
|
|
|
|
// subnetToUID maps subnet to UID.
|
|
subnetToUID aghalg.SortedMap[netip.Prefix, UID]
|
|
}
|
|
|
|
// NewIndex initializes the new instance of client index.
|
|
func NewIndex() (ci *Index) {
|
|
return &Index{
|
|
clientIDToUID: map[string]UID{},
|
|
ipToUID: map[netip.Addr]UID{},
|
|
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
|
|
macToUID: map[macKey]UID{},
|
|
uidToClient: map[UID]*Persistent{},
|
|
}
|
|
}
|
|
|
|
// Add stores information about a persistent client in the index. c must be
|
|
// non-nil and contain UID.
|
|
func (ci *Index) Add(c *Persistent) {
|
|
if (c.UID == UID{}) {
|
|
panic("client must contain uid")
|
|
}
|
|
|
|
for _, id := range c.ClientIDs {
|
|
ci.clientIDToUID[id] = c.UID
|
|
}
|
|
|
|
for _, ip := range c.IPs {
|
|
ci.ipToUID[ip] = c.UID
|
|
}
|
|
|
|
for _, pref := range c.Subnets {
|
|
ci.subnetToUID.Set(pref, c.UID)
|
|
}
|
|
|
|
for _, mac := range c.MACs {
|
|
k := macToKey(mac)
|
|
ci.macToUID[k] = c.UID
|
|
}
|
|
|
|
ci.uidToClient[c.UID] = c
|
|
}
|
|
|
|
// Clashes returns an error if the index contains a different persistent client
|
|
// with at least a single identifier contained by c. c must be non-nil.
|
|
func (ci *Index) Clashes(c *Persistent) (err error) {
|
|
for _, id := range c.ClientIDs {
|
|
existing, ok := ci.clientIDToUID[id]
|
|
if ok && existing != c.UID {
|
|
p := ci.uidToClient[existing]
|
|
|
|
return fmt.Errorf("another client %q uses the same ID %q", p.Name, id)
|
|
}
|
|
}
|
|
|
|
p, ip := ci.clashesIP(c)
|
|
if p != nil {
|
|
return fmt.Errorf("another client %q uses the same IP %q", p.Name, ip)
|
|
}
|
|
|
|
p, s := ci.clashesSubnet(c)
|
|
if p != nil {
|
|
return fmt.Errorf("another client %q uses the same subnet %q", p.Name, s)
|
|
}
|
|
|
|
p, mac := ci.clashesMAC(c)
|
|
if p != nil {
|
|
return fmt.Errorf("another client %q uses the same MAC %q", p.Name, mac)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// clashesIP returns a previous client with the same IP address as c. c must be
|
|
// non-nil.
|
|
func (ci *Index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
|
|
for _, ip := range c.IPs {
|
|
existing, ok := ci.ipToUID[ip]
|
|
if ok && existing != c.UID {
|
|
return ci.uidToClient[existing], ip
|
|
}
|
|
}
|
|
|
|
return nil, netip.Addr{}
|
|
}
|
|
|
|
// clashesSubnet returns a previous client with the same subnet as c. c must be
|
|
// non-nil.
|
|
func (ci *Index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
|
|
for _, s = range c.Subnets {
|
|
var existing UID
|
|
var ok bool
|
|
|
|
ci.subnetToUID.Range(func(p netip.Prefix, uid UID) (cont bool) {
|
|
if s == p {
|
|
existing = uid
|
|
ok = true
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
if ok && existing != c.UID {
|
|
return ci.uidToClient[existing], s
|
|
}
|
|
}
|
|
|
|
return nil, netip.Prefix{}
|
|
}
|
|
|
|
// clashesMAC returns a previous client with the same MAC address as c. c must
|
|
// be non-nil.
|
|
func (ci *Index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) {
|
|
for _, mac = range c.MACs {
|
|
k := macToKey(mac)
|
|
existing, ok := ci.macToUID[k]
|
|
if ok && existing != c.UID {
|
|
return ci.uidToClient[existing], mac
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// Find finds persistent client by string representation of the client ID, IP
|
|
// address, or MAC.
|
|
func (ci *Index) Find(id string) (c *Persistent, ok bool) {
|
|
uid, found := ci.clientIDToUID[id]
|
|
if found {
|
|
return ci.uidToClient[uid], true
|
|
}
|
|
|
|
ip, err := netip.ParseAddr(id)
|
|
if err == nil {
|
|
// MAC addresses can be successfully parsed as IP addresses.
|
|
c, found = ci.findByIP(ip)
|
|
if found {
|
|
return c, true
|
|
}
|
|
}
|
|
|
|
mac, err := net.ParseMAC(id)
|
|
if err == nil {
|
|
return ci.findByMAC(mac)
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// find finds persistent client by IP address.
|
|
func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
|
|
uid, found := ci.ipToUID[ip]
|
|
if found {
|
|
return ci.uidToClient[uid], true
|
|
}
|
|
|
|
ipWithoutZone := ip.WithZone("")
|
|
ci.subnetToUID.Range(func(pref netip.Prefix, id UID) (cont bool) {
|
|
// Remove zone before checking because prefixes strip zones.
|
|
if pref.Contains(ipWithoutZone) {
|
|
uid, found = id, true
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
if found {
|
|
return ci.uidToClient[uid], true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// FindByIPWithoutZone finds a persistent client by IP address without zone. It
|
|
// strips the IPv6 zone index from the stored IP addresses before comparing,
|
|
// because querylog entries don't have it. See TODO on [querylog.logEntry.IP].
|
|
//
|
|
// Note that multiple clients can have the same IP address with different zones.
|
|
// Therefore, the result of this method is indeterminate.
|
|
func (ci *Index) FindByIPWithoutZone(ip netip.Addr) (c *Persistent) {
|
|
if (ip == netip.Addr{}) {
|
|
return nil
|
|
}
|
|
|
|
for addr, uid := range ci.ipToUID {
|
|
if addr.WithZone("") == ip {
|
|
return ci.uidToClient[uid]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// find finds persistent client by MAC.
|
|
func (ci *Index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
|
|
k := macToKey(mac)
|
|
uid, found := ci.macToUID[k]
|
|
if found {
|
|
return ci.uidToClient[uid], true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// Delete removes information about persistent client from the index. c must be
|
|
// non-nil.
|
|
func (ci *Index) Delete(c *Persistent) {
|
|
for _, id := range c.ClientIDs {
|
|
delete(ci.clientIDToUID, id)
|
|
}
|
|
|
|
for _, ip := range c.IPs {
|
|
delete(ci.ipToUID, ip)
|
|
}
|
|
|
|
for _, pref := range c.Subnets {
|
|
ci.subnetToUID.Del(pref)
|
|
}
|
|
|
|
for _, mac := range c.MACs {
|
|
k := macToKey(mac)
|
|
delete(ci.macToUID, k)
|
|
}
|
|
|
|
delete(ci.uidToClient, c.UID)
|
|
}
|