From c1ee2c7e5e8deff4701f8ff2e5742711f4d0d198 Mon Sep 17 00:00:00 2001 From: Stanislav Chzhen Date: Wed, 24 Apr 2024 19:08:54 +0300 Subject: [PATCH] Pull request 2200: 6312-client-ipv6-zone Updates #6312. Squashed commit of the following: commit bd9146ee161a67fa41763070f985e1e73b85823b Merge: 58d2fd98d 856cc40cf Author: Stanislav Chzhen Date: Wed Apr 24 18:09:19 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 58d2fd98d3e82c84638d58dd4d74d13a9a8fbca6 Author: Stanislav Chzhen Date: Wed Apr 24 18:00:56 2024 +0300 client: imp naming commit 922a14b036d829c2775feb7bb3e6beb6aa49692e Merge: 6f4d58fe1 60f48e2d0 Author: Stanislav Chzhen Date: Wed Apr 24 14:29:00 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 6f4d58fe1c42504e8345bff24dbb3f523e8c5f85 Author: Stanislav Chzhen Date: Wed Apr 24 14:27:55 2024 +0300 client: imp docs commit fa292eee828cd6f27f62b782675aa1f998e44518 Author: Stanislav Chzhen Date: Mon Apr 22 19:20:28 2024 +0300 client: fix typo commit 599414be0ccd3f9deb044e022a8ac0006c96b467 Merge: 502571756 762ef4a6d Author: Stanislav Chzhen Date: Mon Apr 22 18:42:06 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit 502571756400a00445086b5ba412e03fca65e39f Author: Stanislav Chzhen Date: Mon Apr 22 18:39:22 2024 +0300 all: imp code; add tests commit 155b2fef500a0d835f49957d9f30b0870712f6f2 Author: Stanislav Chzhen Date: Tue Apr 16 19:56:00 2024 +0300 all: upd chlog; imp code commit 7a4426c5d0a511cd3865884c00328b8c130746bf Merge: e9c1cbb85 48c6242a7 Author: Stanislav Chzhen Date: Tue Apr 16 19:52:00 2024 +0300 Merge branch 'master' into 6312-client-ipv6-zone commit e9c1cbb85e4afa173969d5bedfaaaf92716b7fad Author: Stanislav Chzhen Date: Wed Apr 10 16:23:07 2024 +0300 client: client ipv6 zone --- CHANGELOG.md | 3 + internal/client/index.go | 24 ++++- internal/client/index_internal_test.go | 119 ++++++++++++++++++++----- internal/home/clients.go | 6 +- internal/querylog/entry.go | 1 + 5 files changed, 129 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a180412..d9a6d219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Fixed +- Support for link-local subnets, i.e. `fe80::/16`, as client identifiers + ([#6312]). - Issues with QUIC and HTTP/3 upstreams on older Linux kernel versions ([#6422]). - YouTube restricted mode is not enforced by HTTPS queries on Firefox. @@ -51,6 +53,7 @@ NOTE: Add new changes BELOW THIS COMMENT. [#5345]: https://github.com/AdguardTeam/AdGuardHome/issues/5345 [#5812]: https://github.com/AdguardTeam/AdGuardHome/issues/5812 [#6192]: https://github.com/AdguardTeam/AdGuardHome/issues/6192 +[#6312]: https://github.com/AdguardTeam/AdGuardHome/issues/6312 [#6422]: https://github.com/AdguardTeam/AdGuardHome/issues/6422 [#6854]: https://github.com/AdguardTeam/AdGuardHome/issues/6854 [#6875]: https://github.com/AdguardTeam/AdGuardHome/issues/6875 diff --git a/internal/client/index.go b/internal/client/index.go index c6a17cb3..24ec6e30 100644 --- a/internal/client/index.go +++ b/internal/client/index.go @@ -197,8 +197,10 @@ func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) { return ci.uidToClient[uid], true } + ipWithoutZone := ip.WithZone("") ci.subnetToUID.Range(func(pref netip.Prefix, id UID) (cont bool) { - if pref.Contains(ip) { + // Remove zone before checking because prefixes strip zones. + if pref.Contains(ipWithoutZone) { uid, found = id, true return false @@ -214,6 +216,26 @@ func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) { 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) diff --git a/internal/client/index_internal_test.go b/internal/client/index_internal_test.go index abf38710..4e478462 100644 --- a/internal/client/index_internal_test.go +++ b/internal/client/index_internal_test.go @@ -35,27 +35,49 @@ func TestClientIndex(t *testing.T) { cliID = "client-id" cliMAC = "11:11:11:11:11:11" + + linkLocalIP = "fe80::abcd:abcd:abcd:ab%eth0" + linkLocalSubnet = "fe80::/16" ) - clients := []*Persistent{{ - Name: "client1", - IPs: []netip.Addr{ - netip.MustParseAddr(cliIP1), - netip.MustParseAddr(cliIPv6), - }, - }, { - Name: "client2", - IPs: []netip.Addr{netip.MustParseAddr(cliIP2)}, - Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)}, - }, { - Name: "client_with_mac", - MACs: []net.HardwareAddr{mustParseMAC(cliMAC)}, - }, { - Name: "client_with_id", - ClientIDs: []string{cliID}, - }} + var ( + clientWithBothFams = &Persistent{ + Name: "client1", + IPs: []netip.Addr{ + netip.MustParseAddr(cliIP1), + netip.MustParseAddr(cliIPv6), + }, + } - ci := newIDIndex(clients) + clientWithSubnet = &Persistent{ + Name: "client2", + IPs: []netip.Addr{netip.MustParseAddr(cliIP2)}, + Subnets: []netip.Prefix{netip.MustParsePrefix(cliSubnet)}, + } + + clientWithMAC = &Persistent{ + Name: "client_with_mac", + MACs: []net.HardwareAddr{mustParseMAC(cliMAC)}, + } + + clientWithID = &Persistent{ + Name: "client_with_id", + ClientIDs: []string{cliID}, + } + + clientLinkLocal = &Persistent{ + Name: "client_link_local", + Subnets: []netip.Prefix{netip.MustParsePrefix(linkLocalSubnet)}, + } + ) + + ci := newIDIndex([]*Persistent{ + clientWithBothFams, + clientWithSubnet, + clientWithMAC, + clientWithID, + clientLinkLocal, + }) testCases := []struct { want *Persistent @@ -64,19 +86,23 @@ func TestClientIndex(t *testing.T) { }{{ name: "ipv4_ipv6", ids: []string{cliIP1, cliIPv6}, - want: clients[0], + want: clientWithBothFams, }, { name: "ipv4_subnet", ids: []string{cliIP2, cliSubnetIP}, - want: clients[1], + want: clientWithSubnet, }, { name: "mac", ids: []string{cliMAC}, - want: clients[2], + want: clientWithMAC, }, { name: "client_id", ids: []string{cliID}, - want: clients[3], + want: clientWithID, + }, { + name: "client_link_local_subnet", + ids: []string{linkLocalIP}, + want: clientLinkLocal, }} for _, tc := range testCases { @@ -221,3 +247,52 @@ func TestMACToKey(t *testing.T) { _ = macToKey(mac) }) } + +func TestIndex_FindByIPWithoutZone(t *testing.T) { + var ( + ip = netip.MustParseAddr("fe80::a098:7654:32ef:ff1") + ipWithZone = netip.MustParseAddr("fe80::1ff:fe23:4567:890a%eth2") + ) + + var ( + clientNoZone = &Persistent{ + Name: "client", + IPs: []netip.Addr{ip}, + } + + clientWithZone = &Persistent{ + Name: "client_with_zone", + IPs: []netip.Addr{ipWithZone}, + } + ) + + ci := newIDIndex([]*Persistent{ + clientNoZone, + clientWithZone, + }) + + testCases := []struct { + ip netip.Addr + want *Persistent + name string + }{{ + name: "without_zone", + ip: ip, + want: clientNoZone, + }, { + name: "with_zone", + ip: ipWithZone, + want: clientWithZone, + }, { + name: "zero_address", + ip: netip.Addr{}, + want: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := ci.FindByIPWithoutZone(tc.ip.WithZone("")) + require.Equal(t, tc.want, c) + }) + } +} diff --git a/internal/home/clients.go b/internal/home/clients.go index 1474002b..7a2487ca 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -412,7 +412,11 @@ func (clients *clientsContainer) clientOrArtificial( }() cli, ok := clients.find(id) - if ok { + if !ok { + cli = clients.clientIndex.FindByIPWithoutZone(ip) + } + + if cli != nil { return &querylog.Client{ Name: cli.Name, IgnoreQueryLog: cli.IgnoreQueryLog, diff --git a/internal/querylog/entry.go b/internal/querylog/entry.go index c3c800ed..ed3319b0 100644 --- a/internal/querylog/entry.go +++ b/internal/querylog/entry.go @@ -31,6 +31,7 @@ type logEntry struct { Answer []byte `json:",omitempty"` OrigAnswer []byte `json:",omitempty"` + // TODO(s.chzhen): Use netip.Addr. IP net.IP `json:"IP"` Result filtering.Result