From 4f48680addd22c11bfc4b638523d9ee28183bd6e Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Mon, 6 Jul 2026 18:37:03 +0200 Subject: [PATCH 1/3] Drop malformed TUN packets --- polyamide/device/send.go | 1 + polyamide/device/traffic_control.go | 3 +- polyamide/device/traffic_manip.go | 37 +++++++++++++++++++++-- polyamide/device/traffic_manip_test.go | 41 ++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 polyamide/device/traffic_manip_test.go diff --git a/polyamide/device/send.go b/polyamide/device/send.go index 1a50ee06..3f677e7c 100644 --- a/polyamide/device/send.go +++ b/polyamide/device/send.go @@ -249,6 +249,7 @@ func (device *Device) RoutineReadFromTUN() { } tce := device.GetTCElement() tce.Buffer = bufs[i] + tce.Packet = bufs[i][offset : offset+sizes[i]] tcBufs = append(tcBufs, tce) bufs[i] = device.GetMessageBuffer() diff --git a/polyamide/device/traffic_control.go b/polyamide/device/traffic_control.go index 04147042..6ecb48b9 100644 --- a/polyamide/device/traffic_control.go +++ b/polyamide/device/traffic_control.go @@ -109,8 +109,7 @@ func (device *Device) TCBatch(batch []*TCElement, tcs *TCState) { for i, elem := range batch { // process TC Filters act := TcPass - elem.ParsePacket() - if !elem.Validate() { + if !elem.ParsePacket() || !elem.Validate() { device.Log.Errorf("Found malformed packet, dropping packet") act = TcDrop } else { diff --git a/polyamide/device/traffic_manip.go b/polyamide/device/traffic_manip.go index 23061fc4..89d956eb 100644 --- a/polyamide/device/traffic_manip.go +++ b/polyamide/device/traffic_manip.go @@ -20,10 +20,25 @@ func (elem *TCElement) InitPacket(ver int, len uint16) { elem.SetLength(len) } -func (elem *TCElement) ParsePacket() { - elem.Packet = elem.Buffer[MessageTransportHeaderSize:] +func (elem *TCElement) ParsePacket() bool { + if elem == nil { + return false + } + if len(elem.Packet) == 0 { + if elem.Buffer == nil { + return false + } + elem.Packet = elem.Buffer[MessageTransportHeaderSize:] + } + if !elem.hasPacketHeader() { + return false + } l := elem.GetLength() - elem.Packet = elem.Buffer[MessageTransportHeaderSize : MessageTransportHeaderSize+l] + if int(l) > len(elem.Packet) { + return false + } + elem.Packet = elem.Packet[:l] + return true } func (elem *TCElement) Incoming() bool { @@ -137,6 +152,9 @@ func (elem *TCElement) Validate() bool { if elem == nil || len(elem.Packet) == 0 { return false } + if !elem.hasPacketHeader() { + return false + } ver := elem.GetIPVersion() if ver == 4 { if elem.GetLength() < ipv4.HeaderLen { @@ -154,6 +172,19 @@ func (elem *TCElement) Validate() bool { return true } +func (elem *TCElement) hasPacketHeader() bool { + if elem == nil || len(elem.Packet) < PolyHeaderSize { + return false + } + ver := elem.GetIPVersion() + if ver == 4 { + return len(elem.Packet) >= ipv4.HeaderLen + } else if ver == 6 { + return len(elem.Packet) >= ipv6.HeaderLen + } + return true +} + func (elem *TCElement) TTLBytes() []byte { if elem.GetIPVersion() == 4 { return elem.Packet[8:9] diff --git a/polyamide/device/traffic_manip_test.go b/polyamide/device/traffic_manip_test.go new file mode 100644 index 00000000..bf9e3ad6 --- /dev/null +++ b/polyamide/device/traffic_manip_test.go @@ -0,0 +1,41 @@ +package device + +import ( + "encoding/binary" + "testing" +) + +func TestParsePacketRejectsLengthBeyondReadBuffer(t *testing.T) { + var buf [MaxMessageSize]byte + packet := buf[MessageTransportHeaderSize : MessageTransportHeaderSize+2016] + packet[0] = 6 << 4 + binary.BigEndian.PutUint16(packet[IPv6offsetPayloadLength:IPv6offsetPayloadLength+2], 2010) + + elem := &TCElement{ + Buffer: &buf, + Packet: packet, + } + + if elem.ParsePacket() { + t.Fatal("expected oversized packet to be rejected") + } +} + +func TestParsePacketTrimsToAdvertisedLength(t *testing.T) { + var buf [MaxMessageSize]byte + packet := buf[MessageTransportHeaderSize : MessageTransportHeaderSize+128] + packet[0] = 6 << 4 + binary.BigEndian.PutUint16(packet[IPv6offsetPayloadLength:IPv6offsetPayloadLength+2], 8) + + elem := &TCElement{ + Buffer: &buf, + Packet: packet, + } + + if !elem.ParsePacket() { + t.Fatal("expected valid packet to parse") + } + if len(elem.Packet) != 48 { + t.Fatalf("expected packet length 48, got %d", len(elem.Packet)) + } +} From e2e72716dd5fab75ed729bb20b49c38f44f25d2e Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Mon, 6 Jul 2026 02:48:22 +0200 Subject: [PATCH 2/3] fix: Fix Windows IPv6 alias configuration Use the netsh ipv6 context for IPv6 address aliases instead of the IPv4 interface ip context so Windows nodes can assign their Nylon ULA address. --- core/sys_windows.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/sys_windows.go b/core/sys_windows.go index bbaad91f..81f3601f 100644 --- a/core/sys_windows.go +++ b/core/sys_windows.go @@ -29,10 +29,16 @@ func InitInterface(logger *slog.Logger, ifName string) error { } func ConfigureAlias(logger *slog.Logger, ifName string, addr netip.Addr) error { + if addr.Is6() { + return Exec(logger, "netsh", "interface", "ipv6", "add", "address", ifName, addr.String()) + } return Exec(logger, "netsh", "interface", "ip", "add", "address", ifName, addr.String()) } func RemoveAlias(logger *slog.Logger, ifName string, addr netip.Addr) error { + if addr.Is6() { + return Exec(logger, "netsh", "interface", "ipv6", "delete", "address", ifName, addr.String()) + } return Exec(logger, "netsh", "interface", "ip", "delete", "address", ifName, addr.String()) } From 473d365947786135ed5dc5e84aa4e20f0f54d1d3 Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Mon, 6 Jul 2026 22:31:39 +0200 Subject: [PATCH 3/3] fix(tun): set Windows adapter MTU Wintun adapters can come up with a 65535 IPv6 MTU on Windows, while Nylon internally assumes the configured TUN MTU. That lets the OS hand oversized packets to the device and can trigger malformed packet handling downstream. Set the Windows IP interface MTU from the requested TUN MTU immediately after adapter creation. IPv6 MTU setup is required because Nylon routes IPv6 overlay traffic; IPv4 is set best-effort when available. --- polyamide/tun/tun_windows.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/polyamide/tun/tun_windows.go b/polyamide/tun/tun_windows.go index de65fb44..c92e3229 100644 --- a/polyamide/tun/tun_windows.go +++ b/polyamide/tun/tun_windows.go @@ -12,7 +12,7 @@ import ( "sync" "sync/atomic" "time" - _ "unsafe" + "unsafe" "golang.org/x/sys/windows" "golang.zx2c4.com/wintun" @@ -49,6 +49,9 @@ type NativeTun struct { var ( WintunTunnelType = "WireGuard" WintunStaticRequestedGUID *windows.GUID + modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry") + procSetIpInterfaceEntry = modiphlpapi.NewProc("SetIpInterfaceEntry") ) //go:linkname procyield runtime.procyield @@ -75,6 +78,11 @@ func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu if mtu > 0 { forcedMTU = mtu } + if err := setInterfaceMTU(wt.LUID(), windows.AF_INET6, forcedMTU); err != nil { + wt.Close() + return nil, fmt.Errorf("Error setting IPv6 interface MTU: %w", err) + } + _ = setInterfaceMTU(wt.LUID(), windows.AF_INET, forcedMTU) tun := &NativeTun{ wt: wt, @@ -225,6 +233,24 @@ func (tun *NativeTun) RunningVersion() (version uint32, err error) { return wintun.RunningVersion() } +func setInterfaceMTU(luid uint64, family uint16, mtu int) error { + var row windows.MibIpInterfaceRow + row.Family = family + row.InterfaceLuid = luid + + r1, _, _ := procGetIpInterfaceEntry.Call(uintptr(unsafe.Pointer(&row))) + if r1 != 0 { + return windows.Errno(r1) + } + + row.NlMtu = uint32(mtu) + r1, _, _ = procSetIpInterfaceEntry.Call(uintptr(unsafe.Pointer(&row))) + if r1 != 0 { + return windows.Errno(r1) + } + return nil +} + func (rate *rateJuggler) update(packetLen uint64) { now := nanotime() total := rate.nextByteCount.Add(packetLen)