//go:build with_gvisor && with_awg // lx: regression for the removal of the AmneziaWG-over-WireGuard start guard. // // The guard refused to bring up an AmneziaWG endpoint whose detour chain reached // a WireGuard-based endpoint — and refused *silently*: Start returned nil with // started=false, so the endpoint looked configured but every dial through it // failed with "WireGuard is not ready yet". The root cause it protected against // (a kernel hang on Android) is gone on this graft (ClientBind reserved-gate), // and Android is not a supported platform here at all. // // This test builds a real AmneziaWG endpoint (junk + ranged magic headers) whose // detour points at an outbound of type "wireguard", drives both start stages, // and asserts the endpoint reports itself started. With the guard in place the // first stage short-circuits and started stays false — this test fails. package wireguard import ( "context" "crypto/rand" "encoding/base64" "net" "net/netip" "os" "testing" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/json/badoption" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) // wgTypedOutbound is an adapter.Outbound that reports type "wireguard" — the hop // the guard used to refuse to start behind. Dialling through it always fails: // the point of the test is that the upper endpoint comes UP, not that it carries // traffic (that is the job of the transport-level e2e stand). type wgTypedOutbound struct { adapter.Outbound tag string } func (o *wgTypedOutbound) Type() string { return C.TypeWireGuard } func (o *wgTypedOutbound) Tag() string { return o.tag } func (o *wgTypedOutbound) Dependencies() []string { return nil } func (o *wgTypedOutbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { return nil, os.ErrClosed } func (o *wgTypedOutbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrClosed } // startChainManager resolves tags from a fixed map. adapter.OutboundManager is // embedded so this compiles against either shape of the interface. type startChainManager struct { adapter.OutboundManager byTag map[string]adapter.Outbound } func (m *startChainManager) Outbound(tag string) (adapter.Outbound, bool) { ob, loaded := m.byTag[tag] return ob, loaded } func randomKey(t *testing.T) string { t.Helper() var key [32]byte if _, err := rand.Read(key[:]); err != nil { t.Fatal(err) } // Clamp so wireguard-go accepts it as a curve25519 private key. key[0] &= 248 key[31] = (key[31] & 127) | 64 return base64.StdEncoding.EncodeToString(key[:]) } // TestAmneziaWGOverWireGuardDetourStarts pins the invariant: an AmneziaWG // endpoint detouring through a WireGuard hop must come up like any other. func TestAmneziaWGOverWireGuardDetourStarts(t *testing.T) { ctx := pause.WithDefaultManager(context.Background()) ctx = service.ContextWith[adapter.OutboundManager](ctx, &startChainManager{ byTag: map[string]adapter.Outbound{ "wg-hop": &wgTypedOutbound{tag: "wg-hop"}, }, }) options := option.WireGuardEndpointOptions{ MTU: 1280, Address: badoption.Listable[netip.Prefix]{netip.MustParsePrefix("10.7.0.2/32")}, PrivateKey: randomKey(t), Peers: []option.WireGuardPeer{{ Address: "10.9.9.9", Port: 51820, PublicKey: randomKey(t), AllowedIPs: badoption.Listable[netip.Prefix]{netip.MustParsePrefix("0.0.0.0/0")}, }}, AmneziaWGOptions: option.AmneziaWGOptions{ Jc: 3, Jmin: 8, Jmax: 80, S4: 16, H1: "10-20", H2: "30-40", H3: "50-60", H4: "70-80", }, } options.Detour = "wg-hop" ep, err := NewEndpoint(ctx, nil, log.NewNOPFactory().NewLogger("wg-awg"), "wg-awg", options) if err != nil { t.Fatal("create amneziawg endpoint over a wireguard detour: ", err) } defer ep.Close() if err = ep.Start(adapter.StartStateStart); err != nil { t.Fatal("start stage: ", err) } if err = ep.Start(adapter.StartStatePostStart); err != nil { t.Fatal("post-start stage: ", err) } if !ep.(*Endpoint).started.Load() { t.Fatal("an amneziawg endpoint behind a wireguard hop must start; it is silently held down") } }