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

@ -69,7 +69,10 @@ func (peer *Peer) keepKeyFreshReceiving() {
* Every time the bind is updated a new routine is started for
* IPv4 and IPv6 (separately)
*/
func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.ReceiveFunc) {
func (device *Device) RoutineReceiveIncoming(
maxBatchSize int,
recv conn.ReceiveFunc,
) {
recvName := recv.PrettyName()
defer func() {
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
@ -132,9 +135,14 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
}
// check size of packet
packet := bufsArrs[i][:size]
msgType := binary.LittleEndian.Uint32(packet[:4])
// get message padding and type based on information from S1-S4 and H1-H4
msgType, padding := device.DeterminePacketTypeAndPadding(packet, MessageUnknownType)
if padding > 0 {
copy(packet, packet[padding:])
packet = packet[:len(packet)-padding]
}
switch msgType {
@ -276,7 +284,6 @@ func (device *Device) RoutineHandshake(id int) {
device.log.Verbosef("Routine: handshake worker %d - started", id)
for elem := range device.queue.handshake.c {
// handle cookie fields and ratelimiting
switch elem.msgType {
@ -303,9 +310,14 @@ func (device *Device) RoutineHandshake(id int) {
// consume reply
if peer := entry.peer; peer.isRunning.Load() {
device.log.Verbosef("Receiving cookie response from %s", elem.endpoint.DstToString())
device.log.Verbosef(
"Receiving cookie response from %s",
elem.endpoint.DstToString(),
)
if !peer.cookieGenerator.ConsumeReply(&reply) {
device.log.Verbosef("Could not decrypt invalid cookie response")
device.log.Verbosef(
"Could not decrypt invalid cookie response",
)
}
}
@ -347,9 +359,7 @@ func (device *Device) RoutineHandshake(id int) {
switch elem.msgType {
case MessageInitiationType:
// unmarshal
var msg MessageInitiation
err := msg.unmarshal(elem.packet)
if err != nil {
@ -357,7 +367,8 @@ func (device *Device) RoutineHandshake(id int) {
goto skip
}
// consume initiation
// have to reassign msgType for ranged msgType to work
msg.Type = elem.msgType
peer := device.ConsumeMessageInitiation(&msg, elem.endpoint)
if peer == nil {
@ -389,6 +400,9 @@ func (device *Device) RoutineHandshake(id int) {
goto skip
}
// have to reassign msgType for ranged msgType to work
msg.Type = elem.msgType
// consume response
peer := device.ConsumeMessageResponse(&msg)
@ -505,11 +519,28 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
}
default:
device.log.Verbosef("Packet with invalid IP version from %v", peer)
device.log.Verbosef(
"Packet with invalid IP version from %v",
peer,
)
continue
}
bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)])
bufs = append(
bufs,
elem.buffer[:MessageTransportOffsetContent+len(elem.packet)],
)
}
peer.rxBytes.Add(rxBytesLen)
if validTailPacket >= 0 {
peer.SetEndpointFromPacket(elemsContainer.elems[validTailPacket].endpoint)
peer.keepKeyFreshReceiving()
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketReceived()
}
if dataPacketReceived {
peer.timersDataReceived()
}
peer.rxBytes.Add(rxBytesLen)
@ -536,3 +567,57 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
device.PutInboundElementsContainer(elemsContainer)
}
}
func (device *Device) DeterminePacketTypeAndPadding(packet []byte, expectedType uint32) (uint32, int) {
size := len(packet)
if expectedType == MessageUnknownType || expectedType == MessageInitiationType {
padding := device.paddings.init
header := device.headers.init
if size == padding+MessageInitiationSize {
data := packet[padding:]
if header.Validate(binary.LittleEndian.Uint32(data)) {
return MessageInitiationType, padding
}
}
}
if expectedType == MessageUnknownType || expectedType == MessageResponseType {
padding := device.paddings.response
header := device.headers.response
if size == padding+MessageResponseSize {
data := packet[padding:]
if header.Validate(binary.LittleEndian.Uint32(data)) {
return MessageResponseType, padding
}
}
}
if expectedType == MessageUnknownType || expectedType == MessageCookieReplyType {
padding := device.paddings.cookie
header := device.headers.cookie
if size == padding+MessageCookieReplySize {
data := packet[padding:]
if header.Validate(binary.LittleEndian.Uint32(data)) {
return MessageCookieReplyType, padding
}
}
}
if expectedType == MessageUnknownType || expectedType == MessageTransportType {
padding := device.paddings.transport
header := device.headers.transport
if size >= padding+MessageTransportHeaderSize {
data := packet[padding:]
if header.Validate(binary.LittleEndian.Uint32(data)) {
return MessageTransportType, padding
}
}
}
return MessageUnknownType, 0
}