experimental/windivert: P2.1+P2.2 with WinDivert NETWORK+SOCKET layers

WIP snapshot before pivot to sing-box+TUN. Reached:
- TCP redirect via streamdump pattern (swap+Outbound=0+reinject)
- SOCKET layer for SYN-stage flow detection (avoids FLOW Establish-too-late race)
- Lazy PID→name resolution (catches Update.exe inside procscan tick)
- UDP forward via SOCKS5 UDP ASSOCIATE relay + manual reinject
- Result: chat works, voice times out (Discord IP discovery / RTC handshake fails)

Reason for pivot: WinDivert NAT-reinject pattern has subtle layer-3
semantics issues that DLL-injection / TUN-based proxies sidestep
entirely. Going with embedded sing-box + wintun as the engine —
proven path for Discord voice through SOCKS5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 22:27:54 +03:00
parent 8ceb7775d7
commit 4074e68715
19 changed files with 2666 additions and 62 deletions
+146
View File
@@ -1,6 +1,7 @@
package divert
import (
"encoding/binary"
"net"
"testing"
@@ -112,3 +113,148 @@ func TestParseIPv4TCP_Errors(t *testing.T) {
})
}
}
// helloUDP is a minimum well-formed IPv4 + UDP datagram:
//
// src=10.0.0.1:54321 dst=1.2.3.4:443 payload=4 bytes ABCD
//
// Total length: 20(IP) + 8(UDP) + 4(payload) = 32 bytes.
var helloUDP = []byte{
// IPv4 header (20 bytes, IHL=5)
0x45, 0x00, 0x00, 0x20, 0xab, 0xcd, 0x40, 0x00, 0x40, 0x11, // proto=17 (UDP)
0x00, 0x00, // checksum placeholder
0x0a, 0x00, 0x00, 0x01, // src 10.0.0.1
0x01, 0x02, 0x03, 0x04, // dst 1.2.3.4
// UDP header (8 bytes)
0xd4, 0x31, 0x01, 0xbb, // src=54321 dst=443
0x00, 0x0c, // length=12 (UDP header + 4 payload)
0x00, 0x00, // checksum placeholder
// Payload (4 bytes)
'A', 'B', 'C', 'D',
}
func fillUDPTestChecksums(b []byte) {
// IP checksum
b[10], b[11] = 0, 0
cs := ipChecksum(b[:20])
b[10] = byte(cs >> 8)
b[11] = byte(cs & 0xff)
// UDP checksum (covers UDP header + payload + pseudo-header)
udpLen := int(binary.BigEndian.Uint16(b[24:26]))
b[26], b[27] = 0, 0
cs = udpChecksum(b[:20], b[20:20+udpLen])
if cs == 0 {
cs = 0xFFFF
}
b[26] = byte(cs >> 8)
b[27] = byte(cs & 0xff)
}
func TestParseIPv4UDP_Roundtrip(t *testing.T) {
pkt := make([]byte, len(helloUDP))
copy(pkt, helloUDP)
fillUDPTestChecksums(pkt)
p, err := ParseIPv4UDP(pkt)
require.NoError(t, err)
assert.Equal(t, "10.0.0.1", p.SrcIP.String())
assert.Equal(t, "1.2.3.4", p.DstIP.String())
assert.Equal(t, uint16(54321), p.SrcPort)
assert.Equal(t, uint16(443), p.DstPort)
assert.Equal(t, 20, p.IHL)
assert.Equal(t, uint16(12), p.UDPLen)
}
func TestParseIPv4UDP_Errors(t *testing.T) {
cases := []struct {
name string
b []byte
}{
{"too_short", []byte{0x45}},
{"not_ipv4", []byte{0x60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
{"not_udp", []byte{0x45, 0, 0, 20, 0, 0, 0, 0, 0, 6, /* TCP */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, err := ParseIPv4UDP(c.b)
assert.Error(t, err)
})
}
}
func TestSwapUDPAndSetDstPort(t *testing.T) {
pkt := make([]byte, len(helloUDP))
copy(pkt, helloUDP)
fillUDPTestChecksums(pkt)
require.NoError(t, SwapUDPAndSetDstPort(pkt, 8080))
p, err := ParseIPv4UDP(pkt)
require.NoError(t, err)
assert.Equal(t, "1.2.3.4", p.SrcIP.String(), "src should be original dst after swap")
assert.Equal(t, "10.0.0.1", p.DstIP.String(), "dst should be original src after swap")
assert.Equal(t, uint16(54321), p.SrcPort, "src port unchanged")
assert.Equal(t, uint16(8080), p.DstPort, "dst port set to new value")
// Validate IP checksum recomputed
ipCs := uint16(pkt[10])<<8 | uint16(pkt[11])
pkt[10], pkt[11] = 0, 0
expIP := ipChecksum(pkt[:20])
assert.Equal(t, expIP, ipCs, "IP checksum mismatch")
}
func TestSwapUDPAndSetSrcPort(t *testing.T) {
pkt := make([]byte, len(helloUDP))
copy(pkt, helloUDP)
fillUDPTestChecksums(pkt)
require.NoError(t, SwapUDPAndSetSrcPort(pkt, 50007))
p, err := ParseIPv4UDP(pkt)
require.NoError(t, err)
assert.Equal(t, "1.2.3.4", p.SrcIP.String())
assert.Equal(t, "10.0.0.1", p.DstIP.String())
assert.Equal(t, uint16(50007), p.SrcPort, "src port set to new value")
assert.Equal(t, uint16(443), p.DstPort, "dst port unchanged")
}
func TestBuildIPv4UDPInbound(t *testing.T) {
src := net.IPv4(140, 82, 121, 4) // GitHub IP, just for variety
dst := net.IPv4(192, 168, 1, 50) // local LAN
payload := []byte("hello voice")
pkt, err := BuildIPv4UDPInbound(src, dst, 50007, 50100, payload)
require.NoError(t, err)
// Total length: 20+8+11 = 39
assert.Len(t, pkt, 39)
// Re-parse and verify fields
p, err := ParseIPv4UDP(pkt)
require.NoError(t, err)
assert.Equal(t, "140.82.121.4", p.SrcIP.String())
assert.Equal(t, "192.168.1.50", p.DstIP.String())
assert.Equal(t, uint16(50007), p.SrcPort)
assert.Equal(t, uint16(50100), p.DstPort)
assert.Equal(t, uint16(8+len(payload)), p.UDPLen)
// Payload after headers
assert.Equal(t, payload, pkt[28:])
// IP checksum valid: clearing + recomputing should match
ipCs := uint16(pkt[10])<<8 | uint16(pkt[11])
pkt[10], pkt[11] = 0, 0
expIP := ipChecksum(pkt[:20])
assert.Equal(t, expIP, ipCs, "IP checksum should be valid")
// UDP checksum valid (and non-zero)
udpCs := uint16(pkt[26])<<8 | uint16(pkt[27])
assert.NotEqual(t, uint16(0), udpCs, "UDP checksum should be non-zero (RFC 768 trick)")
}
func TestBuildIPv4UDPInbound_NotIPv4(t *testing.T) {
v6 := net.ParseIP("::1")
_, err := BuildIPv4UDPInbound(v6, net.IPv4(1, 2, 3, 4), 1, 2, []byte("x"))
assert.Error(t, err)
}