diff --git a/handshake_manager.go b/handshake_manager.go index 8d841756..481395a5 100644 --- a/handshake_manager.go +++ b/handshake_manager.go @@ -318,7 +318,7 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered } // Attempt a multiport handshake if we are past the TxHandshakeDelay attempts - if hm.multiPort.TxHandshake && hm.udpRaw != nil && hh.counter >= hm.multiPort.TxHandshakeDelay { + if hm.multiPort.TxHandshake && hm.udpRaw != nil && hh.counter >= hm.multiPort.TxHandshakeDelay && udp.RawSupported(addr) { sentMultiport = true // We need to re-allocate with 8 bytes at the start of SOCK_RAW raw := hostinfo.HandshakePacket[0x80] diff --git a/inside.go b/inside.go index 32b02668..454b6d0c 100644 --- a/inside.go +++ b/inside.go @@ -414,7 +414,7 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType } if remote.IsValid() { - if multiport { + if multiport && udp.RawSupported(remote) { rawOut = rawOut[:len(out)+udp.RawOverhead] port := udpPortGetter.UDPSendPort(f.multiPort.TxPorts) err = f.udpRaw.WriteTo(rawOut, port, remote) @@ -428,7 +428,7 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType ) } } else if hr := hostinfo.GetRemote(); hr.IsValid() { - if multiport { + if multiport && udp.RawSupported(hr) { rawOut = rawOut[:len(out)+udp.RawOverhead] port := udpPortGetter.UDPSendPort(f.multiPort.TxPorts) err = f.udpRaw.WriteTo(rawOut, port, hr) diff --git a/udp/udp_raw.go b/udp/udp_raw.go index 4c5c2879..b9408e8c 100644 --- a/udp/udp_raw.go +++ b/udp/udp_raw.go @@ -1,6 +1,9 @@ package udp -import mathrand "math/rand" +import ( + mathrand "math/rand" + "net/netip" +) type SendPortGetter interface { // UDPSendPort returns the port to use @@ -14,3 +17,11 @@ func (randomSendPort) UDPSendPort(maxPort int) uint16 { } var RandomSendPort = randomSendPort{} + +// RawSupported reports whether (*RawConn).WriteTo can send to addr. The raw +// socket only builds IPv4 headers, so IPv6 destinations must use the normal +// UDP socket. +func RawSupported(addr netip.AddrPort) bool { + a := addr.Addr() + return a.Is4() || a.Is4In6() +} diff --git a/udp/udp_raw_linux.go b/udp/udp_raw_linux.go index 5093dd45..ff23fa12 100644 --- a/udp/udp_raw_linux.go +++ b/udp/udp_raw_linux.go @@ -76,6 +76,10 @@ func NewRawConn(l *slog.Logger, ip string, port int, basePort uint16) (*RawConn, // WriteTo must be called with raw leaving the first `udp.RawOverhead` bytes empty, // for the IP/UDP headers. func (u *RawConn) WriteTo(raw []byte, fromPort uint16, ip netip.AddrPort) error { + if !RawSupported(ip) { + return fmt.Errorf("raw socket does not support sending to %s", ip) + } + var rsa unix.RawSockaddrInet4 rsa.Family = unix.AF_INET rsa.Addr = ip.Addr().As4() diff --git a/udp/udp_raw_test.go b/udp/udp_raw_test.go new file mode 100644 index 00000000..85c086bb --- /dev/null +++ b/udp/udp_raw_test.go @@ -0,0 +1,15 @@ +package udp + +import ( + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRawSupported(t *testing.T) { + assert.True(t, RawSupported(netip.MustParseAddrPort("1.2.3.4:4242"))) + assert.True(t, RawSupported(netip.MustParseAddrPort("[::ffff:1.2.3.4]:4242"))) + assert.False(t, RawSupported(netip.MustParseAddrPort("[2001:db8::1]:4242"))) + assert.False(t, RawSupported(netip.AddrPort{})) +}