lx: re-graft AmneziaWG 2.0 obfuscation onto sagernet/wireguard-go v0.0.3

Migrate the AmneziaWG graft from the old sagernet base (506b763) onto v0.0.3,
the base sing-box 1.14 uses. Strategy: start from v0.0.3, copy the net-new
obfuscation files verbatim from the proven graft (27290b6), take the 6 modified
device/ files wholesale from the graft, keep all tun/* and conn/* from v0.0.3.

Net-new (copied from 27290b6): device/magic-header.go + device/obf*.go (9).
Modified (from 27290b6): device/{send,receive,uapi,device,noise-protocol,cookie}.go.
  - receive.go: reverted two Go-1.22 'range int' loops to the v0.0.3 form (go 1.20).
  - cookie.go: keeps the 4-arg CreateReply(msgType) the grafted send.go calls.
  - noise-protocol.go: MessageEncapsulatingTransportSize=0 (AWG composes without
    the sagernet Bind.Send headroom prepend).

§010 android UDP_GRO guard (fb8d8d8) intentionally DROPPED: v0.0.3 fixes the
receive split-brain at source — bind_std.go now gates all 5 RX/offload paths on
linux||android, and gso_linux.go splits coalesced packets on android. Re-inserting
the guard would disable a now-working android RX-offload path. Requires on-device
re-verification before release.

go.mod: go 1.20, golang.org/x/sys v0.21.0 (v0.0.3 baseline). No amnezia-vpn import
paths remain. Builds clean for linux/android/windows/darwin (library packages).
This commit is contained in:
Leadaxe 2026-06-23 02:31:21 +03:00
parent 19b0d35877
commit e5feca7d61
16 changed files with 1018 additions and 48 deletions

View file

@ -54,10 +54,11 @@ const (
)
const (
MessageInitiationType = 1
MessageResponseType = 2
MessageCookieReplyType = 3
MessageTransportType = 4
MessageUnknownType uint32 = 0
MessageInitiationType uint32 = 1
MessageResponseType uint32 = 2
MessageCookieReplyType uint32 = 3
MessageTransportType uint32 = 4
)
const (
@ -65,7 +66,7 @@ const (
MessageResponseSize = 92 // size of response message
MessageCookieReplySize = 64 // size of cookie reply message
MessageTransportHeaderSize = 16 // size of data preceding content in transport message
MessageEncapsulatingTransportSize = 8 // size of optional, free (for use by conn.Bind.Send()) space preceding the transport header
MessageEncapsulatingTransportSize = 0 // lx: zeroed so AmneziaWG obfuscation composes without sagernet headroom (AWG path doesn't use the Bind.Send prepend)
MessageTransportSize = MessageTransportHeaderSize + poly1305.TagSize // size of empty transport
MessageKeepaliveSize = MessageTransportSize // size of keepalive
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
@ -218,7 +219,7 @@ type Handshake struct {
localEphemeral NoisePrivateKey // ephemeral secret key
localIndex uint32 // used to clear hash-table
remoteIndex uint32 // index for sending
remoteStatic NoisePublicKey // long term key
remoteStatic NoisePublicKey // long term key, never changes, can be accessed without mutex
remoteEphemeral NoisePublicKey // ephemeral public key
precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret
lastTimestamp tai64n.Timestamp
@ -287,8 +288,10 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
handshake.mixHash(handshake.remoteStatic[:])
msgType := device.headers.init.Generate()
msg := MessageInitiation{
Type: MessageInitiationType,
Type: msgType,
Ephemeral: handshake.localEphemeral.publicKey(),
}
@ -466,7 +469,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
}
var msg MessageResponse
msg.Type = MessageResponseType
msg.Type = device.headers.response.Generate()
msg.Sender = handshake.localIndex
msg.Receiver = handshake.remoteIndex