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:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package mtcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *mtcpListener) setReusePort(network, address string, c syscall.RawConn) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ const (
|
|||||||
|
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
mptcp bool
|
mptcp bool
|
||||||
|
reuseport bool
|
||||||
muxCfg *mux.Config
|
muxCfg *mux.Config
|
||||||
backlog int
|
backlog int
|
||||||
keepalive bool
|
keepalive bool
|
||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 l.md.tproxy {
|
||||||
if err := unix.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1); err != nil {
|
if err := unix.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1); err != nil {
|
||||||
l.logger.Errorf("SetsockoptInt(SOL_IP, IP_TRANSPARENT, 1): %v", err)
|
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 {
|
if err := unix.SetsockoptInt(int(fd), unix.SOL_IPV6, unix.IPV6_TRANSPARENT, 1); err != nil {
|
||||||
l.logger.Errorf("SetsockoptInt(SOL_IPV6, IPV6_TRANSPARENT, 1): %v", err)
|
l.logger.Errorf("failed to set IPV6_TRANSPARENT: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.md.reuseport {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
if l.md.tproxy {
|
||||||
return errors.New("TProxy is not available on non-linux platform")
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -8,11 +8,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package tcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *tcpListener) setReusePort(network, address string, c syscall.RawConn) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ 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")
|
||||||
|
|||||||
Reference in New Issue
Block a user