mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
libnet/d/bridge: pass SCTP sock to the proxy
Since commit b3fabedec, the bridge driver maps ports following a 3-step
process: 1. create a socket, and bind it to the host port; 2. create
iptables rules; 3. start the userland proxy (if it's enabled). This
ensures that the port is really free before inserting iptables rules
that could otherwise disrupt host services.
However, this 3-step process wasn't implemented for SCTP, because we had
no way to instiantiate an SCTP listener from an fd. Since
github.com/ishidawataru/sctp@4719921f9, we can.
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -128,18 +127,18 @@ func newProxy(config ProxyConfig) (p Proxy, err error) {
|
||||
p, err = NewUDPProxy(listener, container, ipv)
|
||||
case "sctp":
|
||||
var listener *sctp.SCTPListener
|
||||
if config.ListenSock != nil {
|
||||
// There's no way to construct an SCTPListener from a file descriptor at the moment.
|
||||
// If a socket has been passed in, it's probably from a newer daemon using a version
|
||||
// of the sctp module that does allow it.
|
||||
return nil, errors.New("cannot use supplied SCTP socket, check the latest docker-proxy is in your $PATH")
|
||||
if config.ListenSock == nil {
|
||||
hostAddr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: config.HostIP}}, Port: config.HostPort}
|
||||
listener, err = sctp.ListenSCTP("sctp"+string(ipv), hostAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen on %s: %w", hostAddr, err)
|
||||
}
|
||||
} else {
|
||||
if listener, err = sctp.FileListener(config.ListenSock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
hostAddr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: config.HostIP}}, Port: config.HostPort}
|
||||
container := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: config.ContainerIP}}, Port: config.ContainerPort}
|
||||
listener, err = sctp.ListenSCTP("sctp"+string(ipv), hostAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen on %s: %w", hostAddr, err)
|
||||
}
|
||||
p, err = NewSCTPProxy(listener, container)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported protocol %s", config.Proto)
|
||||
@@ -179,7 +178,11 @@ func parseFlags() ProxyConfig {
|
||||
}
|
||||
|
||||
if useListenFd {
|
||||
_ = syscall.SetNonblock(int(listenSockFd), true)
|
||||
// Unlike the stdlib, passing a non-blocking socket to `sctp.FileListener`
|
||||
// will result in a non-blocking Accept(). So, do not set this flag for SCTP.
|
||||
if config.Proto != "sctp" {
|
||||
_ = syscall.SetNonblock(int(listenSockFd), true)
|
||||
}
|
||||
config.ListenSock = os.NewFile(listenSockFd, "listen-sock")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ishidawataru/sctp"
|
||||
"golang.org/x/sys/unix"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -155,6 +156,65 @@ func udpListener(t *testing.T, nw string, addr *net.UDPAddr) (*os.File, *net.UDP
|
||||
return osFile, l.LocalAddr().(*net.UDPAddr)
|
||||
}
|
||||
|
||||
func sctpListener(t *testing.T, nw string, addr *sctp.SCTPAddr) (*os.File, *sctp.SCTPAddr) {
|
||||
t.Helper()
|
||||
|
||||
var domain int
|
||||
var sa unix.Sockaddr
|
||||
switch nw {
|
||||
case "sctp4":
|
||||
domain = unix.AF_INET
|
||||
sa = &unix.SockaddrInet4{
|
||||
Addr: [4]uint8(addr.IPAddrs[0].IP.To4()),
|
||||
Port: addr.Port,
|
||||
}
|
||||
case "sctp6":
|
||||
domain = unix.AF_INET6
|
||||
sa = &unix.SockaddrInet6{
|
||||
Addr: [16]uint8(addr.IPAddrs[0].IP.To16()),
|
||||
Port: addr.Port,
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unknown SCTP network type: %s", nw)
|
||||
}
|
||||
|
||||
sockfd, err := unix.Socket(domain, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, unix.IPPROTO_SCTP)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = unix.Bind(sockfd, sa)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = unix.Listen(sockfd, -1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
newfd, _, sysErr := unix.Syscall(unix.SYS_FCNTL, uintptr(sockfd), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if sysErr != 0 {
|
||||
t.Fatal(os.NewSyscallError("fcntl", sysErr))
|
||||
}
|
||||
|
||||
err = unix.Close(sockfd)
|
||||
assert.NilError(t, err)
|
||||
|
||||
sockname, err := unix.Getsockname(int(newfd))
|
||||
assert.NilError(t, err)
|
||||
|
||||
var laddr *sctp.SCTPAddr
|
||||
switch sa := sockname.(type) {
|
||||
case *unix.SockaddrInet4:
|
||||
laddr = &sctp.SCTPAddr{
|
||||
IPAddrs: []net.IPAddr{{IP: sa.Addr[:]}},
|
||||
Port: sa.Port,
|
||||
}
|
||||
case *unix.SockaddrInet6:
|
||||
laddr = &sctp.SCTPAddr{
|
||||
IPAddrs: []net.IPAddr{{IP: sa.Addr[:]}},
|
||||
Port: sa.Port,
|
||||
}
|
||||
}
|
||||
|
||||
return os.NewFile(newfd, ""), laddr
|
||||
}
|
||||
|
||||
func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string, halfClose bool) {
|
||||
t.Helper()
|
||||
defer proxy.Close()
|
||||
@@ -414,3 +474,41 @@ func TestSCTP6ProxyNoListener(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
testProxyAt(t, "sctp", proxy, fmt.Sprintf("[%s]:%d", config.HostIP, config.HostPort), false)
|
||||
}
|
||||
|
||||
func TestSCTP4Proxy(t *testing.T) {
|
||||
backend := NewEchoServer(t, "sctp", "127.0.0.1:0", EchoServerOptions{})
|
||||
defer backend.Close()
|
||||
backend.Run()
|
||||
listener, frontendAddr := sctpListener(t, "sctp4", &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.ParseIP("127.0.0.1")}}, Port: 0})
|
||||
backendAddr := backend.LocalAddr().(*sctp.SCTPAddr)
|
||||
config := ProxyConfig{
|
||||
Proto: "sctp",
|
||||
HostIP: frontendAddr.IPAddrs[0].IP,
|
||||
HostPort: frontendAddr.Port,
|
||||
ContainerIP: backendAddr.IPAddrs[0].IP,
|
||||
ContainerPort: backendAddr.Port,
|
||||
ListenSock: listener,
|
||||
}
|
||||
proxy, err := newProxy(config)
|
||||
assert.NilError(t, err)
|
||||
testProxyAt(t, "sctp", proxy, fmt.Sprintf("%s:%d", config.HostIP, config.HostPort), false)
|
||||
}
|
||||
|
||||
func TestSCTP6Proxy(t *testing.T) {
|
||||
backend := NewEchoServer(t, "sctp", "[::1]:0", EchoServerOptions{})
|
||||
defer backend.Close()
|
||||
backend.Run()
|
||||
listener, frontendAddr := sctpListener(t, "sctp6", &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: net.ParseIP("::1")}}, Port: 0})
|
||||
backendAddr := backend.LocalAddr().(*sctp.SCTPAddr)
|
||||
config := ProxyConfig{
|
||||
Proto: "sctp",
|
||||
HostIP: frontendAddr.IPAddrs[0].IP,
|
||||
HostPort: frontendAddr.Port,
|
||||
ContainerIP: backendAddr.IPAddrs[0].IP,
|
||||
ContainerPort: backendAddr.Port,
|
||||
ListenSock: listener,
|
||||
}
|
||||
proxy, err := newProxy(config)
|
||||
assert.NilError(t, err)
|
||||
testProxyAt(t, "sctp", proxy, fmt.Sprintf("[%s]:%d", config.HostIP, config.HostPort), false)
|
||||
}
|
||||
|
||||
@@ -530,19 +530,7 @@ func attemptBindHostPorts(
|
||||
case "udp":
|
||||
pb, err = bindTCPOrUDP(c, port, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
|
||||
case "sctp":
|
||||
if proxyPath == "" {
|
||||
pb, err = bindSCTP(c, port)
|
||||
} else {
|
||||
// TODO(robmry) - it's not currently possible to pass a bound SCTP port
|
||||
// to the userland proxy, because the proxy is not able to convert the
|
||||
// file descriptor into an sctp.SCTPListener (fd is an unexported member
|
||||
// of the struct, and ListenSCTP is the only constructor).
|
||||
// If that changes, remove this.
|
||||
// Until then, it is possible for the proxy to start listening and accept
|
||||
// connections before iptables rules are created that would bypass
|
||||
// the proxy for external connections.
|
||||
pb, err = startSCTPProxy(c, port, proxyPath)
|
||||
}
|
||||
pb, err = bindSCTP(c, port)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown addr type: %s", proto)
|
||||
}
|
||||
@@ -567,7 +555,7 @@ func attemptBindHostPorts(
|
||||
// socket. Listen here anyway because SO_REUSEADDR is set, so bind() won't notice
|
||||
// the problem if a port's bound to both INADDR_ANY and a specific address. (Also
|
||||
// so the binding shows up in "netstat -at".)
|
||||
if err := tcpListenBoundPorts(res, proxyPath); err != nil {
|
||||
if err := listenBoundPorts(res, proxyPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
@@ -684,20 +672,6 @@ func bindSCTP(cfg portBindingReq, port int) (_ portBinding, retErr error) {
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
func startSCTPProxy(cfg portBindingReq, port int, proxyPath string) (_ portBinding, retErr error) {
|
||||
pb := portBinding{PortBinding: cfg.GetCopy()}
|
||||
pb.HostPort = uint16(port)
|
||||
pb.HostPortEnd = pb.HostPort
|
||||
pb.childHostIP = cfg.childHostIP
|
||||
|
||||
var err error
|
||||
pb.stopProxy, err = startProxy(pb.childPortBinding(), proxyPath, nil)
|
||||
if err != nil {
|
||||
return portBinding{}, err
|
||||
}
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// configPortDriver passes the port binding's details to rootlesskit, and updates the
|
||||
// port binding with callbacks to remove the rootlesskit config (or marks the binding as
|
||||
// unsupported by rootlesskit).
|
||||
@@ -731,26 +705,27 @@ func configPortDriver(ctx context.Context, pbs []portBinding, pdc portDriverClie
|
||||
return nil
|
||||
}
|
||||
|
||||
func tcpListenBoundPorts(pbs []portBinding, proxyPath string) error {
|
||||
somaxconn := 0
|
||||
if proxyPath != "" {
|
||||
somaxconn = -1 // silently capped to "/proc/sys/net/core/somaxconn"
|
||||
}
|
||||
func listenBoundPorts(pbs []portBinding, proxyPath string) error {
|
||||
for i := range pbs {
|
||||
if pbs[i].boundSocket == nil || pbs[i].rootlesskitUnsupported || pbs[i].Proto != types.TCP {
|
||||
if pbs[i].boundSocket == nil || pbs[i].rootlesskitUnsupported || pbs[i].Proto == types.UDP {
|
||||
continue
|
||||
}
|
||||
rc, err := pbs[i].boundSocket.SyscallConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("raw conn not available on TCP socket: %w", err)
|
||||
return fmt.Errorf("raw conn not available on %s socket: %w", pbs[i].Proto, err)
|
||||
}
|
||||
if errC := rc.Control(func(fd uintptr) {
|
||||
somaxconn := 0
|
||||
// SCTP sockets do not support somaxconn=0
|
||||
if proxyPath != "" || pbs[i].Proto == types.SCTP {
|
||||
somaxconn = -1 // silently capped to "/proc/sys/net/core/somaxconn"
|
||||
}
|
||||
err = syscall.Listen(int(fd), somaxconn)
|
||||
}); errC != nil {
|
||||
return fmt.Errorf("failed to Control TCP socket: %w", err)
|
||||
return fmt.Errorf("failed to Control %s socket: %w", pbs[i].Proto, err)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on TCP socket: %w", err)
|
||||
return fmt.Errorf("failed to listen on %s socket: %w", pbs[i].Proto, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user