From f2e2bffff813cb9dce29f14f3506a8a12ef172d3 Mon Sep 17 00:00:00 2001 From: Aptimex Date: Thu, 22 Jan 2026 20:30:03 -0700 Subject: [PATCH 1/4] working udp responses --- src/cmd/serve.go | 3 +++ src/transport/udp/udp.go | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/cmd/serve.go b/src/cmd/serve.go index 86b6f35..399f3b4 100644 --- a/src/cmd/serve.go +++ b/src/cmd/serve.go @@ -557,6 +557,9 @@ func (c serveCmdConfig) Run() { configureLocalhostForwarding(localhostAddr, s) + // The UDP handler needs to know the localhost IP for proper connection mapping. + udp.LocalhostIP = localhostAddr + if localhostAddr.IsLoopback() { fmt.Printf("=== WARNING: %s is a loopback IP. It will probably not work for Localhost Forwarding ===\n", localhostAddr.String()) diff --git a/src/transport/udp/udp.go b/src/transport/udp/udp.go index d905f42..a892309 100644 --- a/src/transport/udp/udp.go +++ b/src/transport/udp/udp.go @@ -48,6 +48,8 @@ var sourceMapLock = sync.RWMutex{} var connMap = make(map[udpConn](chan *stack.PacketBuffer)) var connMapLock = sync.RWMutex{} +var LocalhostIP netip.Addr + type Config struct { Tnet *netstack.Net StackLock *sync.Mutex @@ -276,6 +278,12 @@ func sendResponse(conn udpConn, data []byte, s *stack.Stack) { var err error var ipv4Layer *layers.IPv4 var ipv6Layer *layers.IPv6 + var fakeSource bool = false + + // The IPTables DNAT rule does not properly apply to UDP return packets for some reason, need to manually fake source IP. + if LocalhostIP.IsValid() && conn.Dest.Addr() == netip.MustParseAddr("127.0.0.1") { + fakeSource = true + } udpLayer := &layers.UDP{ SrcPort: layers.UDPPort(conn.Dest.Port()), @@ -296,7 +304,13 @@ func sendResponse(conn udpConn, data []byte, s *stack.Stack) { ipv4Layer = &layers.IPv4{ Version: 4, //IHL: 5, - SrcIP: conn.Dest.Addr().AsSlice(), + SrcIP: func () []byte { + if fakeSource { + return LocalhostIP.AsSlice() + } else { + return conn.Dest.Addr().AsSlice() + } + }(), DstIP: conn.Source.Addr().AsSlice(), Protocol: layers.IPProtocolUDP, TTL: 64, From 7a61f97d0ec970f9eff441cd1c2f0886390c69a1 Mon Sep 17 00:00:00 2001 From: Aptimex Date: Fri, 23 Jan 2026 12:35:14 -0700 Subject: [PATCH 2/4] fix localhost unreachable ICMP responses --- src/transport/udp/udp.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/transport/udp/udp.go b/src/transport/udp/udp.go index a892309..ebc0ef7 100644 --- a/src/transport/udp/udp.go +++ b/src/transport/udp/udp.go @@ -3,6 +3,7 @@ package udp import ( + "bytes" "fmt" "log" "net" @@ -363,6 +364,7 @@ func sendUnreachable(packet *stack.PacketBuffer, s *stack.Stack) { var ipv4Layer *layers.IPv4 var ipv6Layer *layers.IPv6 var icmpLayer []byte + var fakeSource bool = false defer packet.DecRef() netHeader := packet.Network() @@ -399,6 +401,7 @@ func sendUnreachable(packet *stack.PacketBuffer, s *stack.Stack) { }, }).Marshal(nil) ipv6Layer.Length = uint16(len(icmpLayer)) + } else { ipv4Layer = &layers.IPv4{} ipv4Layer, err = transport.GetNetworkLayer[header.IPv4](netHeader, ipv4Layer) @@ -406,19 +409,37 @@ func sendUnreachable(packet *stack.PacketBuffer, s *stack.Stack) { log.Println("could not decode Network header:", err) return } + + // The IPTables DNAT rule does not properly apply to these ICMP return packets, need to manually fake source IP in two places + if LocalhostIP.IsValid() && bytes.Equal(ipv4Layer.DstIP, net.ParseIP("127.0.0.1").To4()) { + fakeSource = true + } + ipv4Layer = &layers.IPv4{ Version: 4, IHL: 5, - SrcIP: ipv4Layer.DstIP, + SrcIP: func () []byte { + if fakeSource { + return LocalhostIP.AsSlice() + } else { + return ipv4Layer.DstIP + } + }(), DstIP: ipv4Layer.SrcIP, Protocol: layers.IPProtocolICMPv4, TTL: 64, } + + if fakeSource { + dest := tcpip.AddrFrom4Slice(LocalhostIP.AsSlice()) + netHeader.SetDestinationAddress(dest) + } ipv4Header, ok := netHeader.(header.IPv4) if !ok { - log.Println("could not type assert IPv6 Network Header") + log.Println("could not type assert IPv4 Network Header") return } + icmpLayer, err = (&neticmp.Message{ Type: netipv4.ICMPTypeDestinationUnreachable, Code: layers.ICMPv4CodePort, From bd393db1e3a8e4f1dce0a0b57154634582cba0e4 Mon Sep 17 00:00:00 2001 From: Aptimex Date: Fri, 23 Jan 2026 12:41:30 -0700 Subject: [PATCH 3/4] better comparison method --- src/transport/udp/udp.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/transport/udp/udp.go b/src/transport/udp/udp.go index ebc0ef7..51f70d6 100644 --- a/src/transport/udp/udp.go +++ b/src/transport/udp/udp.go @@ -3,7 +3,6 @@ package udp import ( - "bytes" "fmt" "log" "net" @@ -411,7 +410,7 @@ func sendUnreachable(packet *stack.PacketBuffer, s *stack.Stack) { } // The IPTables DNAT rule does not properly apply to these ICMP return packets, need to manually fake source IP in two places - if LocalhostIP.IsValid() && bytes.Equal(ipv4Layer.DstIP, net.ParseIP("127.0.0.1").To4()) { + if LocalhostIP.IsValid() && ipv4Layer.DstIP.Equal(net.ParseIP("127.0.0.1").To4()) { fakeSource = true } From 6be20a0638b16c7fc83dd8c6d4a428b4af271031 Mon Sep 17 00:00:00 2001 From: amerril Date: Mon, 2 Feb 2026 16:53:46 -0700 Subject: [PATCH 4/4] fix readme whitespace bug --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8240e5..5242449 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ In this diagram, the Client has generated and installed WireGuard configuration - [Outbound Connections](#outbound-connections) - [Simple Mode](#simple-mode) - [Serve](#serve) - - [Status](#status) + - [Status](#status) - [Add Server (Optional)](#add-server-optional) - [Add Client (Optional)](#add-client-optional) - [Expose (Port Forwarding)](#expose-port-forwarding)