diff --git a/go/reuse_port/freeport.go b/go/reuse_port/freeport.go new file mode 100644 index 00000000000..40df0e406f3 --- /dev/null +++ b/go/reuse_port/freeport.go @@ -0,0 +1,45 @@ +package reuseport + +import ( + "context" + "errors" + "net" +) + +func FreeTcpPort() (*net.TCPListener, int, error) { + l, err := ReusePortListenConfig.Listen(context.Background(), "tcp", "localhost:0") + if err != nil { + return nil, 0, err + } + + tl, ok := l.(*net.TCPListener) + if !ok { + return nil, 0, errors.New("Bad Cast") + } + + addr, ok := l.Addr().(*net.TCPAddr) + if !ok { + return nil, 0, errors.New("Bad Cast") + } + + return tl, addr.Port, nil +} + +func FreeUdpPort() (*net.UDPConn, int, error) { + l, err := ReusePortListenConfig.ListenPacket(context.Background(), "udp", "localhost:0") + if err != nil { + return nil, 0, err + } + + uc, ok := l.(*net.UDPConn) + if !ok { + return nil, 0, errors.New("Bad Cast") + } + + addr, ok := l.LocalAddr().(*net.UDPAddr) + if !ok { + return nil, 0, errors.New("Bad Cast") + } + + return uc, addr.Port, nil +} diff --git a/go/reuse_port/freeport_test.go b/go/reuse_port/freeport_test.go new file mode 100644 index 00000000000..3c261304b10 --- /dev/null +++ b/go/reuse_port/freeport_test.go @@ -0,0 +1,40 @@ +package reuseport + +import ( + "context" + "testing" +) + +func TestFreeTcpPort(t *testing.T) { + l1, _, err := FreeTcpPort() + if err != nil { + t.Fatalf("%s", err.Error()) + } + + defer l1.Close() + l2, err := ReusePortListenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + if err != nil { + t.Fatalf("%s", err.Error()) + } + + if l2.Addr().String() != l1.Addr().String() { + t.Fatalf("Not the same address") + } +} + +func TestFreeUdpPort(t *testing.T) { + l1, _, err := FreeUdpPort() + if err != nil { + t.Fatalf("%s", err.Error()) + } + + defer l1.Close() + l2, err := ReusePortListenConfig.ListenPacket(context.Background(), "udp", l1.LocalAddr().String()) + if err != nil { + t.Fatalf("%s", err.Error()) + } + + if l2.LocalAddr().String() != l1.LocalAddr().String() { + t.Fatalf("Not the same address") + } +} diff --git a/go/reuse_port/reuse_port.go b/go/reuse_port/reuse_port.go new file mode 100644 index 00000000000..2553d1476a5 --- /dev/null +++ b/go/reuse_port/reuse_port.go @@ -0,0 +1,24 @@ +package reuseport + +import ( + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func reusePort(network, address string, conn syscall.RawConn) error { + var opErr error + err := conn.Control(func(fd uintptr) { + opErr = syscall.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + if err != nil { + return err + } + + return opErr +} + +var ReusePortListenConfig = net.ListenConfig{ + Control: reusePort, +}