From 91a04eabf7408addb30cca17ea86c07092d9e102 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 18 Sep 2024 22:20:46 +0200 Subject: [PATCH] Fix UDP Socket Binding Issue on Linux for IPv4 and IPv6 (#582) * Fix UDP Socket Binding Issue on Linux for IPv4 and IPv6 On Linux, when using UDP sockets, both IPv6 and IPv4 addresses are bound to the same socket by default. This can cause issues when sending and receiving packets across different address families. Specifically, if we receive a packet from an IPv4 address, attempting to send a response using an IPv6 address fails. --- net/connUDP.go | 7 ++++ net/connUDP_internal_test.go | 78 ++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/net/connUDP.go b/net/connUDP.go index 9bda1ea8..7c4587c9 100644 --- a/net/connUDP.go +++ b/net/connUDP.go @@ -516,6 +516,13 @@ func (c *UDPConn) writeTo(raddr *net.UDPAddr, cm *ControlMessage, buffer []byte) // because the connection is already established. return c.connection.Write(buffer) } + // On Linux, UDP network binds both IPv6 and IPv4 addresses to the same socket. + // When receiving a packet from an IPv4 address, we cannot send a packet from an IPv6 address. + // Therefore, we wrap the connection using an IPv4 packet connection (packetConn). + if !IsIPv6(raddr.IP) && c.packetConn.IsIPv6() { + pc := packetConnIPv4{packetConn: ipv4.NewPacketConn(c.connection)} + return pc.WriteTo(buffer, cm, raddr) + } return c.packetConn.WriteTo(buffer, cm, raddr) } diff --git a/net/connUDP_internal_test.go b/net/connUDP_internal_test.go index 14720752..71dc8347 100644 --- a/net/connUDP_internal_test.go +++ b/net/connUDP_internal_test.go @@ -3,6 +3,7 @@ package net import ( "context" "net" + "runtime" "strconv" "sync" "testing" @@ -19,7 +20,9 @@ const ( ) func TestUDPConnWriteWithContext(t *testing.T) { - peerAddr := "127.0.0.1:2154" + iface := getInterfaceIndex(t) + ifaceIpv4 := getIfaceAddr(t, iface, true) + peerAddr := ifaceIpv4.String() + ":2154" b, err := net.ResolveUDPAddr(udpNetwork, peerAddr) require.NoError(t, err) @@ -27,9 +30,10 @@ func TestUDPConnWriteWithContext(t *testing.T) { ctxCancel() type args struct { - ctx context.Context - udpAddr *net.UDPAddr - buffer []byte + ctx context.Context + listenNetwork string + udpAddr *net.UDPAddr + buffer []byte } tests := []struct { name string @@ -37,35 +41,42 @@ func TestUDPConnWriteWithContext(t *testing.T) { wantErr bool }{ { - name: "valid", + name: "valid - udp4 network", args: args{ - ctx: context.Background(), - udpAddr: b, - buffer: []byte("hello world"), + ctx: context.Background(), + listenNetwork: udp4Network, + udpAddr: b, + buffer: []byte("hello world"), }, }, { name: "cancelled", args: args{ - ctx: ctxCanceled, - buffer: []byte("hello world"), + ctx: ctxCanceled, + listenNetwork: udp4Network, + buffer: []byte("hello world"), }, wantErr: true, }, } + if runtime.GOOS == "linux" { + tests = append(tests, struct { + name string + args args + wantErr bool + }{ + name: "valid - udp network", + args: args{ + ctx: context.Background(), + listenNetwork: udpNetwork, + udpAddr: b, + buffer: []byte("hello world"), + }, + }) + } - a, err := net.ResolveUDPAddr(udpNetwork, "127.0.0.1:") - require.NoError(t, err) - l1, err := net.ListenUDP(udpNetwork, a) - require.NoError(t, err) - c1 := NewUDPConn(udpNetwork, l1, WithErrors(func(err error) { t.Log(err) })) - defer func() { - errC := c1.Close() - require.NoError(t, errC) - }() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - l2, err := net.ListenUDP(udpNetwork, b) require.NoError(t, err) c2 := NewUDPConn(udpNetwork, l2, WithErrors(func(err error) { t.Log(err) })) @@ -84,7 +95,19 @@ func TestUDPConnWriteWithContext(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err = c1.WriteWithContext(tt.args.ctx, tt.args.udpAddr, tt.args.buffer) + a, err := net.ResolveUDPAddr(tt.args.listenNetwork, ":") + require.NoError(t, err) + l1, err := net.ListenUDP(tt.args.listenNetwork, a) + require.NoError(t, err) + c1 := NewUDPConn(tt.args.listenNetwork, l1, WithErrors(func(err error) { t.Log(err) })) + defer func() { + errC := c1.Close() + require.NoError(t, errC) + }() + + err = c1.WriteWithOptions(tt.args.buffer, WithContext(ctx), WithRemoteAddr(tt.args.udpAddr), WithControlMessage(&ControlMessage{ + IfIndex: iface.Index, + })) c1.LocalAddr() c1.RemoteAddr() @@ -312,18 +335,21 @@ func isActiveMulticastInterface(iface net.Interface) bool { return false } -func TestUDPConnWriteToAddr(t *testing.T) { +func getInterfaceIndex(t *testing.T) net.Interface { ifaces, err := net.Interfaces() require.NoError(t, err) - var iface net.Interface for _, i := range ifaces { t.Logf("interface name:%v, flags: %v", i.Name, i.Flags) if isActiveMulticastInterface(i) { - iface = i - break + return i } } - require.NotEmpty(t, iface) + require.Fail(t, "No suitable interface found") + return net.Interface{} +} + +func TestUDPConnWriteToAddr(t *testing.T) { + iface := getInterfaceIndex(t) type args struct { iface *net.Interface src net.IP