Files
MHSanaei ecd817658c 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.
2026-11-01 10:02:11 +01:00

65 lines
2.1 KiB
Go

package amneziawgnet
import (
"net/netip"
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
)
// PeerIndex resolves a decapsulated connection's tunnel-internal source
// address back to the peer it belongs to, the same role Xray-core's own
// wireguard proxy's GetUserByAddr plays -- sourced here from an
// amneziawg.Instance's own Peers (already carries Email per peer, no new
// data needed) rather than a separate user table.
type PeerIndex struct {
entries []peerIndexEntry
}
type peerIndexEntry struct {
prefix netip.Prefix
peer amneziawg.Peer
}
// NewPeerIndex builds a lookup index from peers' AllowedIPs. Entries with an
// unparseable AllowedIPs value are skipped rather than failing the whole
// index -- by the time an Instance reaches this package, AllowedIPs has
// already been accepted at save time (see internal/amneziawg's own
// validation), so a bad entry here would only mean stale/manually-edited
// data, not something worth refusing to serve the rest of the peers over.
func NewPeerIndex(peers []amneziawg.Peer) *PeerIndex {
idx := &PeerIndex{}
for _, p := range peers {
for _, a := range p.AllowedIPs {
prefix, err := netip.ParsePrefix(a)
if err != nil {
continue
}
idx.entries = append(idx.entries, peerIndexEntry{prefix: prefix, peer: p})
}
}
return idx
}
// Lookup returns the peer whose AllowedIPs most specifically contains addr --
// the same longest-prefix-match rule a real AmneziaWG interface's own
// AllowedIPs routing table uses for outbound packets, applied here in
// reverse to attribute an inbound (tunnel-internal-source) packet back to
// its owning peer.
func (idx *PeerIndex) Lookup(addr netip.Addr) (amneziawg.Peer, bool) {
bestBits := -1
var bestPeer amneziawg.Peer
for _, e := range idx.entries {
// 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()
bestPeer = e.peer
}
if bestBits < 0 {
return amneziawg.Peer{}, false
}
return bestPeer, true
}