feat(listener): add SO_REUSEPORT support for TCP listeners (#654)

Add opt-in reuseport metadata option for tcp, mtcp, and redirect/tcp
listeners, allowing a GOST listener on a specific IP to coexist with
another process on the same port but different address (e.g., Nginx on
0.0.0.0:443 + GOST on 127.0.0.1:443). With SO_REUSEPORT, the kernel
routes to the most specific address match.

On Unix, sets SO_REUSEPORT via ListenConfig.Control before bind().
On Windows, the option is a no-op (SO_REUSEADDR is the default).

Closes go-gost/gost#654
This commit is contained in:
ginuerzh
2026-06-22 21:42:00 +08:00
parent 4057ac54b8
commit 99a0f7323d
14 changed files with 122 additions and 12 deletions
+3
View File
@@ -59,6 +59,9 @@ func (l *mtcpListener) Init(md md.Metadata) (err error) {
lc.SetMultipathTCP(true) lc.SetMultipathTCP(true)
l.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP()) l.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP())
} }
if l.md.reuseport {
lc.Control = l.setReusePort
}
ln, err := lc.Listen(context.Background(), network, l.options.Addr) ln, err := lc.Listen(context.Background(), network, l.options.Addr)
if err != nil { if err != nil {
return return
+17
View File
@@ -0,0 +1,17 @@
//go:build !windows
package mtcp
import (
"syscall"
"golang.org/x/sys/unix"
)
func (l *mtcpListener) setReusePort(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
l.logger.Errorf("failed to set SO_REUSEPORT: %v", err)
}
})
}
+11
View File
@@ -0,0 +1,11 @@
//go:build windows
package mtcp
import (
"syscall"
)
func (l *mtcpListener) setReusePort(network, address string, c syscall.RawConn) error {
return nil
}
+4 -2
View File
@@ -13,8 +13,9 @@ const (
) )
type metadata struct { type metadata struct {
mptcp bool mptcp bool
muxCfg *mux.Config reuseport bool
muxCfg *mux.Config
backlog int backlog int
keepalive bool keepalive bool
keepaliveIdle time.Duration keepaliveIdle time.Duration
@@ -24,6 +25,7 @@ type metadata struct {
func (l *mtcpListener) parseMetadata(md md.Metadata) (err error) { func (l *mtcpListener) parseMetadata(md md.Metadata) (err error) {
l.md.mptcp = mdutil.GetBool(md, "mptcp") l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.reuseport = mdutil.GetBool(md, "reuseport")
l.md.muxCfg = &mux.Config{ l.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"), Version: mdutil.GetInt(md, "mux.version"),
+1 -1
View File
@@ -53,7 +53,7 @@ func (l *redirectListener) Init(md md.Metadata) (err error) {
network = "tcp4" network = "tcp4"
} }
lc := net.ListenConfig{} lc := net.ListenConfig{}
if l.md.tproxy { if l.md.tproxy || l.md.reuseport {
lc.Control = l.control lc.Control = l.control
} }
if l.md.mptcp { if l.md.mptcp {
+9
View File
@@ -2,8 +2,17 @@ package tcp
import ( import (
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
func (l *redirectListener) control(network, address string, c syscall.RawConn) error { func (l *redirectListener) control(network, address string, c syscall.RawConn) error {
if l.md.reuseport {
return c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
l.logger.Errorf("failed to set SO_REUSEPORT: %v", err)
}
})
}
return nil return nil
} }
+11 -4
View File
@@ -8,11 +8,18 @@ import (
func (l *redirectListener) control(network, address string, c syscall.RawConn) error { func (l *redirectListener) control(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1); err != nil { if l.md.tproxy {
l.logger.Errorf("SetsockoptInt(SOL_IP, IP_TRANSPARENT, 1): %v", err) if err := unix.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1); err != nil {
l.logger.Errorf("failed to set IP_TRANSPARENT: %v", err)
}
if err := unix.SetsockoptInt(int(fd), unix.SOL_IPV6, unix.IPV6_TRANSPARENT, 1); err != nil {
l.logger.Errorf("failed to set IPV6_TRANSPARENT: %v", err)
}
} }
if err := unix.SetsockoptInt(int(fd), unix.SOL_IPV6, unix.IPV6_TRANSPARENT, 1); err != nil { if l.md.reuseport {
l.logger.Errorf("SetsockoptInt(SOL_IPV6, IPV6_TRANSPARENT, 1): %v", err) if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
l.logger.Errorf("failed to set SO_REUSEPORT: %v", err)
}
} }
}) })
} }
+14 -2
View File
@@ -1,12 +1,24 @@
//go:build !linux && !darwin //go:build !linux && !darwin && !windows
package tcp package tcp
import ( import (
"errors" "errors"
"syscall" "syscall"
"golang.org/x/sys/unix"
) )
func (l *redirectListener) control(network, address string, c syscall.RawConn) error { func (l *redirectListener) control(network, address string, c syscall.RawConn) error {
return errors.New("TProxy is not available on non-linux platform") if l.md.tproxy {
return errors.New("TProxy is not available on non-linux platform")
}
if l.md.reuseport {
return c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
l.logger.Errorf("failed to set SO_REUSEPORT: %v", err)
}
})
}
return nil
} }
+14
View File
@@ -0,0 +1,14 @@
package tcp
import (
"errors"
"syscall"
)
func (l *redirectListener) control(network, address string, c syscall.RawConn) error {
if l.md.tproxy {
return errors.New("TProxy is not available on non-linux platform")
}
// SO_REUSEADDR is the default on Windows, so reuseport is a no-op.
return nil
}
+4 -2
View File
@@ -6,13 +6,15 @@ import (
) )
type metadata struct { type metadata struct {
tproxy bool tproxy bool
mptcp bool mptcp bool
reuseport bool
} }
func (l *redirectListener) parseMetadata(md mdata.Metadata) (err error) { func (l *redirectListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.tproxy = mdutil.GetBool(md, "tproxy") l.md.tproxy = mdutil.GetBool(md, "tproxy")
l.md.mptcp = mdutil.GetBool(md, "mptcp") l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.reuseport = mdutil.GetBool(md, "reuseport")
return return
} }
+3
View File
@@ -56,6 +56,9 @@ func (l *tcpListener) Init(md md.Metadata) (err error) {
lc.SetMultipathTCP(true) lc.SetMultipathTCP(true)
l.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP()) l.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP())
} }
if l.md.reuseport {
lc.Control = l.setReusePort
}
ln, err := lc.Listen(context.Background(), network, l.options.Addr) ln, err := lc.Listen(context.Background(), network, l.options.Addr)
if err != nil { if err != nil {
return return
+17
View File
@@ -0,0 +1,17 @@
//go:build !windows
package tcp
import (
"syscall"
"golang.org/x/sys/unix"
)
func (l *tcpListener) setReusePort(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
l.logger.Errorf("failed to set SO_REUSEPORT: %v", err)
}
})
}
+11
View File
@@ -0,0 +1,11 @@
//go:build windows
package tcp
import (
"syscall"
)
func (l *tcpListener) setReusePort(network, address string, c syscall.RawConn) error {
return nil
}
+3 -1
View File
@@ -8,7 +8,8 @@ import (
) )
type metadata struct { type metadata struct {
mptcp bool mptcp bool
reuseport bool
keepalive bool keepalive bool
keepaliveIdle time.Duration keepaliveIdle time.Duration
@@ -18,6 +19,7 @@ type metadata struct {
func (l *tcpListener) parseMetadata(md md.Metadata) (err error) { func (l *tcpListener) parseMetadata(md md.Metadata) (err error) {
l.md.mptcp = mdutil.GetBool(md, "mptcp") l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.reuseport = mdutil.GetBool(md, "reuseport")
l.md.keepalive = mdutil.GetBool(md, "keepalive") l.md.keepalive = mdutil.GetBool(md, "keepalive")
l.md.keepaliveIdle = mdutil.GetDuration(md, "keepalive.idle") l.md.keepaliveIdle = mdutil.GetDuration(md, "keepalive.idle")