diff --git a/conn/controlfns_linux.go b/conn/controlfns_linux.go index 752fbca..7602665 100644 --- a/conn/controlfns_linux.go +++ b/conn/controlfns_linux.go @@ -60,6 +60,15 @@ func init() { // Attempt to enable UDP_GRO func(network, address string, c syscall.RawConn) error { + // lx(010): skip UDP_GRO on android. The GRO receive path in bind_std.go + // is gated on runtime.GOOS=="linux", which is false on android — so a + // coalesced super-packet is never split and corrupts the WG stream + // (download dies). Belt-and-suspenders with the rxOffload guard in + // features_linux.go. TX/GSO untouched. + // See SPECS/010-B-O-WG_ENDPOINT_GRO_SPLIT_BRAIN. + if runtime.GOOS == "android" { + return nil + } c.Control(func(fd uintptr) { _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPGRO, 1) }) diff --git a/conn/features_linux.go b/conn/features_linux.go index 513202e..b74c398 100644 --- a/conn/features_linux.go +++ b/conn/features_linux.go @@ -7,6 +7,7 @@ package conn import ( "net" + "runtime" "golang.org/x/sys/unix" ) @@ -29,6 +30,15 @@ func supportsUDPOffload(conn *net.UDPConn) (txOffload, rxOffload bool) { return } txOffload = true + // lx(010): never advertise RX offload on android. runtime.GOOS=="android" + // (not "linux"), so the GRO receive dispatcher in bind_std.go (gated on + // GOOS=="linux") is dead there — a coalesced GRO super-packet would be read + // as one datagram and corrupt the WG transport stream, killing download. + // Confirmed on device (CPH2411/Android-15: rxOffload=true, dispatch=single). + // TX is left untouched. See SPECS/010-B-O-WG_ENDPOINT_GRO_SPLIT_BRAIN. + if runtime.GOOS == "android" { + return + } opt, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPGRO) if errSyscall != nil { return