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

@ -6,8 +6,11 @@
package device
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"math/big"
"net"
"os"
"sync"
@ -127,15 +130,48 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
return err
}
buf := make([]byte, MessageEncapsulatingTransportSize+MessageInitiationSize)
packet := buf[MessageEncapsulatingTransportSize:]
_ = msg.marshal(packet)
var sendBuffer [][]byte
for _, ipacket := range peer.device.ipackets {
if ipacket != nil {
buf := make([]byte, ipacket.ObfuscatedLen(0))
ipacket.Obfuscate(buf, nil)
sendBuffer = append(sendBuffer, buf)
}
}
jc := peer.device.junk.count
jmin := peer.device.junk.min
jmax := peer.device.junk.max
for i := 0; i < jc; i++ {
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1)))
n := int(nBig.Int64()) + jmin
buf := make([]byte, n)
rand.Read(buf)
sendBuffer = append(sendBuffer, buf)
}
var buf [MessageInitiationSize]byte
writer := bytes.NewBuffer(buf[:0])
binary.Write(writer, binary.LittleEndian, msg)
packet := writer.Bytes()
peer.cookieGenerator.AddMacs(packet)
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
err = peer.SendBuffers([][]byte{buf})
if padding := peer.device.paddings.init; padding > 0 {
buf := make([]byte, padding+len(packet))
rand.Read(buf[:padding])
copy(buf[padding:], packet)
packet = buf
}
sendBuffer = append(sendBuffer, packet)
err = peer.SendBuffers(sendBuffer)
if err != nil {
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
}
@ -157,9 +193,11 @@ func (peer *Peer) SendHandshakeResponse() error {
return err
}
buf := make([]byte, MessageEncapsulatingTransportSize+MessageResponseSize)
packet := buf[MessageEncapsulatingTransportSize:]
_ = response.marshal(packet)
var buf [MessageResponseSize]byte
writer := bytes.NewBuffer(buf[:0])
binary.Write(writer, binary.LittleEndian, response)
packet := writer.Bytes()
peer.cookieGenerator.AddMacs(packet)
err = peer.BeginSymmetricSession()
@ -172,8 +210,15 @@ func (peer *Peer) SendHandshakeResponse() error {
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketSent()
if padding := peer.device.paddings.response; padding > 0 {
buf := make([]byte, padding+len(packet))
rand.Read(buf[:padding])
copy(buf[padding:], packet)
packet = buf
}
// TODO: allocation could be avoided
err = peer.SendBuffers([][]byte{buf})
err = peer.SendBuffers([][]byte{packet})
if err != nil {
peer.device.log.Errorf("%v - Failed to send handshake response: %v", peer, err)
}
@ -184,18 +229,33 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
msgType := device.headers.cookie.Generate()
reply, err := device.cookieChecker.CreateReply(
initiatingElem.packet,
sender,
initiatingElem.endpoint.DstToBytes(),
msgType,
)
if err != nil {
device.log.Errorf("Failed to create cookie reply: %v", err)
return err
}
buf := make([]byte, MessageEncapsulatingTransportSize+MessageCookieReplySize)
packet := buf[MessageEncapsulatingTransportSize:]
_ = reply.marshal(packet)
// TODO: allocation could be avoided
device.net.bind.Send([][]byte{buf}, initiatingElem.endpoint, MessageEncapsulatingTransportSize)
var buf [MessageCookieReplySize]byte
writer := bytes.NewBuffer(buf[:0])
binary.Write(writer, binary.LittleEndian, reply)
packet := writer.Bytes()
if padding := device.paddings.cookie; padding > 0 {
buf := make([]byte, padding+len(packet))
rand.Read(buf[:padding])
copy(buf[padding:], packet)
packet = buf
}
// TODO: allocation could be avoided
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint, 0)
return nil
}
@ -477,13 +537,15 @@ func (device *Device) RoutineEncryption(id int) {
for elemsContainer := range device.queue.encryption.c {
for _, elem := range elemsContainer.elems {
// populate header fields
header := elem.buffer[MessageEncapsulatingTransportSize : MessageEncapsulatingTransportSize+MessageTransportHeaderSize]
header := elem.buffer[:MessageTransportHeaderSize]
fieldType := header[0:4]
fieldReceiver := header[4:8]
fieldNonce := header[8:16]
binary.LittleEndian.PutUint32(fieldType, MessageTransportType)
msgType := device.headers.transport.Generate()
binary.LittleEndian.PutUint32(fieldType, msgType)
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
@ -500,9 +562,6 @@ func (device *Device) RoutineEncryption(id int) {
elem.packet,
nil,
)
// re-slice packet to include encapsulating transport space
elem.packet = elem.buffer[:MessageEncapsulatingTransportSize+len(elem.packet)]
}
elemsContainer.Unlock()
}
@ -544,6 +603,15 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
if len(elem.packet) != MessageKeepaliveSize {
dataSent = true
}
if padding := device.paddings.transport; padding > 0 {
// elem.packet is stored at the start of elem.buffer
// with zero padding
for i := len(elem.packet) - 1; i >= 0; i-- {
elem.buffer[i+padding] = elem.buffer[i]
}
rand.Read(elem.buffer[:padding])
elem.packet = elem.buffer[:padding+len(elem.packet)]
}
bufs = append(bufs, elem.packet)
}
@ -554,6 +622,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
if dataSent {
peer.timersDataSent()
}
for _, elem := range elemsContainer.elems {
device.PutMessageBuffer(elem.buffer)
device.PutOutboundElement(elem)