mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(amneziawg): attribute an equal-prefix source to the peer the device picks
PeerIndex names the peer a source address came from, but for two peers holding the same prefix, which only a default route can be after the overlap check, it kept the first while amneziawg-go's allowed-IPs table hands the prefix to the peer configured last. Traffic was then billed to a different client than the one the device accepted it from. An equal-length match now goes to the later peer, as in the device.
This commit is contained in:
@@ -49,7 +49,9 @@ func (idx *PeerIndex) Lookup(addr netip.Addr) (amneziawg.Peer, bool) {
|
||||
bestBits := -1
|
||||
var bestPeer amneziawg.Peer
|
||||
for _, e := range idx.entries {
|
||||
if e.prefix.Bits() <= bestBits || !e.prefix.Contains(addr) {
|
||||
// Equal length: the later peer wins, as in the device's allowed-IPs
|
||||
// table, which buildUAPIConfig fills from the same peers in this order.
|
||||
if e.prefix.Bits() < bestBits || !e.prefix.Contains(addr) {
|
||||
continue
|
||||
}
|
||||
bestBits = e.prefix.Bits()
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package amneziawgnet
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
|
||||
)
|
||||
|
||||
// TestPeerIndexLookupMatchesDeviceAllowedIPs catches Lookup attributing a
|
||||
// prefix two peers both carry to a peer the device accepts no such source from.
|
||||
func TestPeerIndexLookupMatchesDeviceAllowedIPs(t *testing.T) {
|
||||
serverPriv, serverPub, err := wireguard.GenerateWireguardKeypair()
|
||||
if err != nil {
|
||||
t.Fatalf("generate server keypair: %v", err)
|
||||
}
|
||||
var peers []amneziawg.Peer
|
||||
for _, p := range []amneziawg.Peer{
|
||||
{Email: "first@example.com", AllowedIPs: []string{"10.202.0.2/32", "fd00:202::2/128", "0.0.0.0/0", "::/0"}},
|
||||
{Email: "last@example.com", AllowedIPs: []string{"10.202.0.3/32", "fd00:202::3/128", "0.0.0.0/0", "::/0"}},
|
||||
} {
|
||||
if _, p.PublicKey, err = wireguard.GenerateWireguardKeypair(); err != nil {
|
||||
t.Fatalf("generate peer keypair: %v", err)
|
||||
}
|
||||
peers = append(peers, p)
|
||||
}
|
||||
|
||||
dev, err := NewDevice(amneziawg.Instance{
|
||||
Id: 1,
|
||||
PrivateKey: serverPriv,
|
||||
PublicKey: serverPub,
|
||||
Address: []string{"10.202.0.1/24"},
|
||||
MTU: 1420,
|
||||
Peers: peers,
|
||||
}, DeviceOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("NewDevice: %v", err)
|
||||
}
|
||||
defer dev.Close()
|
||||
|
||||
dump, err := dev.IpcGet()
|
||||
if err != nil {
|
||||
t.Fatalf("IpcGet: %v", err)
|
||||
}
|
||||
_, states := parseUAPIDump(dump)
|
||||
|
||||
idx := NewPeerIndex(peers)
|
||||
for _, tc := range []struct{ addr, prefix string }{
|
||||
{"10.99.0.1", "0.0.0.0/0"},
|
||||
{"fd00:99::1", "::/0"},
|
||||
{"10.202.0.2", "10.202.0.2/32"},
|
||||
{"10.202.0.3", "10.202.0.3/32"},
|
||||
{"fd00:202::2", "fd00:202::2/128"},
|
||||
{"fd00:202::3", "fd00:202::3/128"},
|
||||
} {
|
||||
t.Run(tc.addr, func(t *testing.T) {
|
||||
got, ok := idx.Lookup(netip.MustParseAddr(tc.addr))
|
||||
if !ok {
|
||||
t.Fatalf("Lookup(%s) found no peer", tc.addr)
|
||||
}
|
||||
gotHex, err := wireguard.KeyToHex(got.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("KeyToHex: %v", err)
|
||||
}
|
||||
if !slices.Contains(states[gotHex].allowedIPs, tc.prefix) {
|
||||
t.Errorf("Lookup(%s) = %s, but the device holds %s for another peer", tc.addr, got.Email, tc.prefix)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user