diff --git a/reuseport/control_default.go b/reuseport/control_default.go new file mode 100644 index 00000000..9eca225e --- /dev/null +++ b/reuseport/control_default.go @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +//go:build !unix && !windows + +package reuseport + +import ( + "syscall" +) + +func Control(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/reuseport/control_unix.go b/reuseport/control_unix.go new file mode 100644 index 00000000..e74d2911 --- /dev/null +++ b/reuseport/control_unix.go @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +//go:build unix + +package reuseport + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +func Control(network, address string, conn syscall.RawConn) error { + return conn.Control(func(fd uintptr) { + err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if err != nil { + return + } + _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) +} diff --git a/reuseport/control_windows.go b/reuseport/control_windows.go new file mode 100644 index 00000000..c8a0557f --- /dev/null +++ b/reuseport/control_windows.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +//go:build windows + +package reuseport + +import ( + "syscall" + + "golang.org/x/sys/windows" +) + +func Control(network, address string, c syscall.RawConn) (err error) { + return c.Control(func(fd uintptr) { + _ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) + }) +} diff --git a/reuseport/reuse_test.go b/reuseport/reuse_test.go new file mode 100644 index 00000000..41ce6873 --- /dev/null +++ b/reuseport/reuse_test.go @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +//go:build unix || windows + +package reuseport + +import ( + "context" + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func testDialFromListeningTCPPort(t *testing.T, network, host string) { + t.Helper() + + lc := net.ListenConfig{ + Control: Control, + } + ctx := context.Background() + + // Allocate a listener that supplies the source address. + ll, err := lc.Listen(ctx, network, host+":0") + assert.NoError(t, err) + + // Allocate another listener that supplies the destination address. + rl, err := lc.Listen(ctx, network, host+":0") + assert.NoError(t, err) + + // Create a dialer do bind to local address. + d := net.Dialer{ + LocalAddr: ll.Addr(), + Control: Control, + } + + // Dial. + c, err := d.Dial(network, rl.Addr().String()) + assert.NoError(t, err) + + assert.NoError(t, c.Close()) +} + +func testDialFromListeningUDPPort(t *testing.T, network, host string) { + t.Helper() + + lc := net.ListenConfig{ + Control: Control, + } + ctx := context.Background() + + // Allocate a listener that supplies the source address. + ll, err := lc.ListenPacket(ctx, network, host+":0") + assert.NoError(t, err) + + // Allocate another listener that supplies the destination address. + rl, err := lc.ListenPacket(ctx, network, host+":0") + assert.NoError(t, err) + + // Create a dialer do bind to local address. + d := net.Dialer{ + LocalAddr: ll.LocalAddr(), + Control: Control, + } + + // Dial. + c, err := d.Dial(network, rl.LocalAddr().String()) + assert.NoError(t, err) + + assert.NoError(t, c.Close()) +} + +func TestDialFromListeningPortTCP4(t *testing.T) { + testDialFromListeningTCPPort(t, "tcp", "localhost") +} + +func TestDialFromListeningPortTCP6(t *testing.T) { + testDialFromListeningTCPPort(t, "tcp6", "[::1]") +} + +func TestDialFromListeningPortUdp4(t *testing.T) { + testDialFromListeningUDPPort(t, "udp", "localhost") +} + +func TestDialFromListeningPortUdp6(t *testing.T) { + testDialFromListeningUDPPort(t, "udp6", "[::1]") +} diff --git a/reuseport/reuseport.go b/reuseport/reuseport.go new file mode 100644 index 00000000..213de12d --- /dev/null +++ b/reuseport/reuseport.go @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2026 The Pion community +// SPDX-License-Identifier: MIT + +// Package reuseport implements an OS-dependent reuse-port helper. +package reuseport