mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
7a41c59494
* fix(amneziawg): honor inbound listen when binding UDP socket AmneziaWG inbounds ignored the listen field and always opened awgconn.NewDefaultBind(), so multi-IP hosts replied from the primary address and handshakes to a secondary IP never completed (#6367). Carry Inbound.Listen on amneziawg.Instance, open a Bind pinned to that address (wildcard when empty/0.0.0.0/::), and include listen in addressFingerprint so edits rebuild the Device. Fixes #6367 * fix(amneziawg): fall back to wildcard when listen is unusable Invalid or non-local listen values no longer hard-fail inbound startup; treat ::0/[::0] as wildcards and normalize listen in the bind fingerprint. * fix(amneziawg): use ListenConfig.ListenPacket for noctx --------- Co-authored-by: mrchatam <mrchatam@users.noreply.github.com> Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package amneziawgnet
|
|
|
|
import (
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
awgconn "github.com/amnezia-vpn/amneziawg-go/v3/conn"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
|
)
|
|
|
|
func TestParseListenAddr(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
pinned bool
|
|
want string
|
|
}{
|
|
{in: "", pinned: false},
|
|
{in: " ", pinned: false},
|
|
{in: "0.0.0.0", pinned: false},
|
|
{in: "::", pinned: false},
|
|
{in: "::0", pinned: false},
|
|
{in: "[::]", pinned: false},
|
|
{in: "[::0]", pinned: false},
|
|
{in: "127.0.0.1", pinned: true, want: "127.0.0.1"},
|
|
{in: "::1", pinned: true, want: "::1"},
|
|
{in: "[::1]", pinned: true, want: "::1"},
|
|
{in: "not-an-ip", pinned: false},
|
|
{in: "/var/run/awg.sock", pinned: false},
|
|
}
|
|
for _, tc := range cases {
|
|
addr, ok := parseListenAddr(tc.in)
|
|
if ok != tc.pinned {
|
|
t.Fatalf("parseListenAddr(%q) pinned=%v, want %v", tc.in, ok, tc.pinned)
|
|
}
|
|
if tc.pinned && addr.String() != tc.want {
|
|
t.Fatalf("parseListenAddr(%q) = %s, want %s", tc.in, addr, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewListenBindPinsSpecificAddress(t *testing.T) {
|
|
bind := newListenBind("127.0.0.1")
|
|
pb, ok := bind.(*pinnedBind)
|
|
if !ok {
|
|
t.Fatalf("bind type = %T, want *pinnedBind", bind)
|
|
}
|
|
fns, port, err := pb.Open(0)
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer pb.Close()
|
|
if len(fns) != 1 {
|
|
t.Fatalf("ReceiveFuncs = %d, want 1", len(fns))
|
|
}
|
|
if port == 0 {
|
|
t.Fatal("expected a concrete ephemeral port")
|
|
}
|
|
|
|
laddr := pb.conn.LocalAddr().(*net.UDPAddr)
|
|
got := laddr.AddrPort().Addr().Unmap()
|
|
if got.String() != "127.0.0.1" {
|
|
t.Fatalf("LocalAddr = %v, want 127.0.0.1", got)
|
|
}
|
|
|
|
clash := newListenBind("127.0.0.1")
|
|
if _, _, err := clash.Open(port); err == nil {
|
|
clash.Close()
|
|
t.Fatalf("Open(%d) unexpectedly succeeded on an already-bound address", port)
|
|
}
|
|
}
|
|
|
|
func TestNewListenBindWildcardUsesDefault(t *testing.T) {
|
|
for _, listen := range []string{"", "0.0.0.0", "::", "::0", "[::]", "hostname.example", "203.0.113.10", "not-an-ip"} {
|
|
bind := newListenBind(listen)
|
|
if _, ok := bind.(*pinnedBind); ok {
|
|
t.Fatalf("newListenBind(%q) returned pinnedBind, want default StdNetBind", listen)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPinnedBindRoundTrip(t *testing.T) {
|
|
server := newListenBind("127.0.0.1")
|
|
recvFns, port, err := server.Open(0)
|
|
if err != nil {
|
|
t.Fatalf("server Open: %v", err)
|
|
}
|
|
defer server.Close()
|
|
|
|
client, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
|
|
if err != nil {
|
|
t.Fatalf("client listen: %v", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
payload := []byte("hello-awg-listen")
|
|
dst := net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))
|
|
ap, err := netip.ParseAddrPort(dst)
|
|
if err != nil {
|
|
t.Fatalf("ParseAddrPort: %v", err)
|
|
}
|
|
if _, err := client.WriteToUDPAddrPort(payload, ap); err != nil {
|
|
t.Fatalf("client write: %v", err)
|
|
}
|
|
|
|
bufs := [][]byte{make([]byte, 1500)}
|
|
sizes := make([]int, 1)
|
|
eps := make([]awgconn.Endpoint, 1)
|
|
n, err := recvFns[0](bufs, sizes, eps)
|
|
if err != nil {
|
|
t.Fatalf("receive: %v", err)
|
|
}
|
|
if n != 1 || sizes[0] != len(payload) {
|
|
t.Fatalf("receive n=%d size=%d, want 1/%d", n, sizes[0], len(payload))
|
|
}
|
|
if string(bufs[0][:sizes[0]]) != string(payload) {
|
|
t.Fatalf("payload = %q, want %q", bufs[0][:sizes[0]], payload)
|
|
}
|
|
|
|
reply := []byte("pong")
|
|
if err := server.Send([][]byte{reply}, eps[0]); err != nil {
|
|
t.Fatalf("Send: %v", err)
|
|
}
|
|
_ = client.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
buf := make([]byte, 1500)
|
|
rn, _, err := client.ReadFromUDP(buf)
|
|
if err != nil {
|
|
t.Fatalf("client read: %v", err)
|
|
}
|
|
if string(buf[:rn]) != string(reply) {
|
|
t.Fatalf("reply = %q, want %q", buf[:rn], reply)
|
|
}
|
|
}
|
|
|
|
func TestAddressFingerprintIncludesListen(t *testing.T) {
|
|
base := amneziawg.Instance{
|
|
MTU: 1420,
|
|
Address: []string{"10.8.1.1/24"},
|
|
Obfuscation: amneziawg.Obfuscation31{},
|
|
}
|
|
a := addressFingerprint(base)
|
|
base.Listen = "127.0.0.1"
|
|
b := addressFingerprint(base)
|
|
if a == b {
|
|
t.Fatalf("listen edit did not change addressFingerprint: %q", a)
|
|
}
|
|
base.Listen = "0.0.0.0"
|
|
if addressFingerprint(base) != a {
|
|
t.Fatal("wildcard spellings must share the empty-listen fingerprint")
|
|
}
|
|
base.Listen = "hostname.example"
|
|
if addressFingerprint(base) != a {
|
|
t.Fatal("unusable listen must fingerprint like wildcard fallback")
|
|
}
|
|
}
|
|
|
|
var _ awgconn.Bind = (*pinnedBind)(nil)
|