all: client persistent list

This commit is contained in:
Stanislav Chzhen 2024-04-19 15:37:15 +03:00
parent 48c6242a7b
commit ccc423b296
4 changed files with 77 additions and 54 deletions

View File

@ -6,6 +6,7 @@ import (
"net/netip"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/golibs/errors"
)
// macKey contains MAC as byte array of 6, 8, or 20 bytes.
@ -28,6 +29,9 @@ func macToKey(mac net.HardwareAddr) (key macKey) {
// Index stores all information about persistent clients.
type Index struct {
// nameToUID maps client name to UID.
nameToUID map[string]UID
// clientIDToUID maps client ID to UID.
clientIDToUID map[string]UID
@ -47,6 +51,7 @@ type Index struct {
// NewIndex initializes the new instance of client index.
func NewIndex() (ci *Index) {
return &Index{
nameToUID: map[string]UID{},
clientIDToUID: map[string]UID{},
ipToUID: map[netip.Addr]UID{},
subnetToUID: aghalg.NewSortedMap[netip.Prefix, UID](subnetCompare),
@ -62,6 +67,8 @@ func (ci *Index) Add(c *Persistent) {
panic("client must contain uid")
}
ci.nameToUID[c.Name] = c.UID
for _, id := range c.ClientIDs {
ci.clientIDToUID[id] = c.UID
}
@ -184,13 +191,23 @@ func (ci *Index) Find(id string) (c *Persistent, ok bool) {
mac, err := net.ParseMAC(id)
if err == nil {
return ci.findByMAC(mac)
return ci.FindByMAC(mac)
}
return nil, false
}
// find finds persistent client by IP address.
// FindByName finds persistent client by name.
func (ci *Index) FindByName(name string) (c *Persistent, found bool) {
uid, found := ci.nameToUID[name]
if found {
return ci.uidToClient[uid], true
}
return nil, false
}
// findByIP finds persistent client by IP address.
func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
uid, found := ci.ipToUID[ip]
if found {
@ -214,8 +231,8 @@ func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
return nil, false
}
// find finds persistent client by MAC.
func (ci *Index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
// FindByMAC 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 {
@ -228,6 +245,8 @@ func (ci *Index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
// Delete removes information about persistent client from the index. c must be
// non-nil.
func (ci *Index) Delete(c *Persistent) {
delete(ci.nameToUID, c.Name)
for _, id := range c.ClientIDs {
delete(ci.clientIDToUID, id)
}
@ -247,3 +266,30 @@ func (ci *Index) Delete(c *Persistent) {
delete(ci.uidToClient, c.UID)
}
// Size returns the number of persistent clients.
func (ci *Index) Size() (n int) {
return len(ci.uidToClient)
}
// Range calls f for each persistent client.
func (ci *Index) Range(f func(c *Persistent) (cont bool)) {
for _, c := range ci.uidToClient {
if !f(c) {
return
}
}
}
// CloseUpstreams closes upstream configurations of persistent clients.
func (ci *Index) CloseUpstreams() (err error) {
var errs []error
for _, c := range ci.uidToClient {
err = c.CloseUpstreams()
if err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}

View File

@ -24,7 +24,6 @@ import (
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"golang.org/x/exp/maps"
)
// DHCP is an interface for accessing DHCP lease data the [clientsContainer]
@ -46,10 +45,6 @@ type DHCP interface {
// 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).
list map[string]*client.Persistent // name -> client
// clientIndex stores information about persistent clients.
clientIndex *client.Index
@ -101,11 +96,10 @@ func (clients *clientsContainer) Init(
arpDB arpdb.Interface,
filteringConf *filtering.Config,
) (err error) {
if clients.list != nil {
if clients.clientIndex != nil {
log.Fatal("clients.list != nil")
}
clients.list = map[string]*client.Persistent{}
clients.runtimeIndex = client.NewRuntimeIndex()
clients.clientIndex = client.NewIndex()
@ -301,9 +295,9 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
clients.lock.Lock()
defer clients.lock.Unlock()
objs = make([]*clientObject, 0, len(clients.list))
for _, cli := range clients.list {
o := &clientObject{
objs = make([]*clientObject, 0, clients.clientIndex.Size())
clients.clientIndex.Range(func(cli *client.Persistent) (cont bool) {
objs = append(objs, &clientObject{
Name: cli.Name,
BlockedServices: cli.BlockedServices.Clone(),
@ -324,10 +318,10 @@ func (clients *clientsContainer) forConfig() (objs []*clientObject) {
IgnoreStatistics: cli.IgnoreStatistics,
UpstreamsCacheEnabled: cli.UpstreamsCacheEnabled,
UpstreamsCacheSize: cli.UpstreamsCacheSize,
}
})
objs = append(objs, o)
}
return true
})
// Maps aren't guaranteed to iterate in the same order each time, so the
// above loop can generate different orderings when writing to the config
@ -542,11 +536,9 @@ func (clients *clientsContainer) findDHCP(ip netip.Addr) (c *client.Persistent,
return nil, false
}
for _, c = range clients.list {
_, found := slices.BinarySearchFunc(c.MACs, foundMAC, slices.Compare[net.HardwareAddr])
if found {
return c, true
}
c, found := clients.clientIndex.FindByMAC(foundMAC)
if found {
return c, true
}
return nil, false
@ -625,7 +617,9 @@ func (clients *clientsContainer) add(c *client.Persistent) (ok bool, err error)
defer clients.lock.Unlock()
// check Name index
_, ok = clients.list[c.Name]
//
// TODO(s.chzhen): Use [client.Index.Clashes].
_, ok = clients.clientIndex.FindByName(c.Name)
if ok {
return false, nil
}
@ -639,17 +633,13 @@ func (clients *clientsContainer) add(c *client.Persistent) (ok bool, err error)
clients.addLocked(c)
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs(), len(clients.list))
log.Debug("clients: added %q: ID:%q [%d]", c.Name, c.IDs(), clients.clientIndex.Size())
return true, nil
}
// addLocked c to the indexes. clients.lock is expected to be locked.
func (clients *clientsContainer) addLocked(c *client.Persistent) {
// update Name index
clients.list[c.Name] = c
// update ID index
clients.clientIndex.Add(c)
}
@ -658,8 +648,7 @@ func (clients *clientsContainer) remove(name string) (ok bool) {
clients.lock.Lock()
defer clients.lock.Unlock()
var c *client.Persistent
c, ok = clients.list[name]
c, ok := clients.clientIndex.FindByName(name)
if !ok {
return false
}
@ -676,9 +665,6 @@ func (clients *clientsContainer) removeLocked(c *client.Persistent) {
log.Error("client container: removing client %s: %s", c.Name, err)
}
// Update the name index.
delete(clients.list, c.Name)
// Update the ID index.
clients.clientIndex.Delete(c)
}
@ -696,7 +682,7 @@ func (clients *clientsContainer) update(prev, c *client.Persistent) (err error)
// Check the name index.
if prev.Name != c.Name {
_, ok := clients.list[c.Name]
_, ok := clients.clientIndex.FindByName(c.Name)
if ok {
return errors.Error("client already exists")
}
@ -883,18 +869,5 @@ func (clients *clientsContainer) addFromSystemARP() {
// close gracefully closes all the client-specific upstream configurations of
// the persistent clients.
func (clients *clientsContainer) close() (err error) {
persistent := maps.Values(clients.list)
slices.SortFunc(persistent, func(a, b *client.Persistent) (res int) {
return strings.Compare(a.Name, b.Name)
})
var errs []error
for _, cli := range persistent {
if err = cli.CloseUpstreams(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
return clients.clientIndex.CloseUpstreams()
}

View File

@ -145,8 +145,9 @@ func TestClients(t *testing.T) {
cliNewIP = netip.MustParseAddr(cliNew)
)
prev, ok := clients.list["client1"]
prev, ok := clients.clientIndex.FindByName("client1")
require.True(t, ok)
require.NotNil(t, prev)
err := clients.update(prev, &client.Persistent{
Name: "client1",
@ -160,8 +161,9 @@ func TestClients(t *testing.T) {
assert.Equal(t, clients.clientSource(cliNewIP), client.SourcePersistent)
prev, ok = clients.list["client1"]
prev, ok = clients.clientIndex.FindByName("client1")
require.True(t, ok)
require.NotNil(t, prev)
err = clients.update(prev, &client.Persistent{
Name: "client1-renamed",
@ -177,7 +179,7 @@ func TestClients(t *testing.T) {
assert.Equal(t, "client1-renamed", c.Name)
assert.True(t, c.UseOwnSettings)
nilCli, ok := clients.list["client1"]
nilCli, ok := clients.clientIndex.FindByName("client1")
require.False(t, ok)
assert.Nil(t, nilCli)

View File

@ -96,10 +96,12 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
clients.lock.Lock()
defer clients.lock.Unlock()
for _, c := range clients.list {
clients.clientIndex.Range(func(c *client.Persistent) (cont bool) {
cj := clientToJSON(c)
data.Clients = append(data.Clients, cj)
}
return true
})
clients.runtimeIndex.Range(func(rc *client.Runtime) (cont bool) {
src, host := rc.Info()
@ -406,7 +408,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht
clients.lock.Lock()
defer clients.lock.Unlock()
prev, ok = clients.list[dj.Name]
prev, ok = clients.clientIndex.FindByName(dj.Name)
}()
if !ok {