summaryrefslogtreecommitdiffstats
path: root/src/net/mptcpsock_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/mptcpsock_linux.go')
-rw-r--r--src/net/mptcpsock_linux.go127
1 files changed, 127 insertions, 0 deletions
diff --git a/src/net/mptcpsock_linux.go b/src/net/mptcpsock_linux.go
new file mode 100644
index 0000000..b2ac3ee
--- /dev/null
+++ b/src/net/mptcpsock_linux.go
@@ -0,0 +1,127 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package net
+
+import (
+ "context"
+ "errors"
+ "internal/poll"
+ "internal/syscall/unix"
+ "sync"
+ "syscall"
+)
+
+var (
+ mptcpOnce sync.Once
+ mptcpAvailable bool
+ hasSOLMPTCP bool
+)
+
+// These constants aren't in the syscall package, which is frozen
+const (
+ _IPPROTO_MPTCP = 0x106
+ _SOL_MPTCP = 0x11c
+ _MPTCP_INFO = 0x1
+)
+
+func supportsMultipathTCP() bool {
+ mptcpOnce.Do(initMPTCPavailable)
+ return mptcpAvailable
+}
+
+// Check that MPTCP is supported by attempting to create an MPTCP socket and by
+// looking at the returned error if any.
+func initMPTCPavailable() {
+ s, err := sysSocket(syscall.AF_INET, syscall.SOCK_STREAM, _IPPROTO_MPTCP)
+ switch {
+ case errors.Is(err, syscall.EPROTONOSUPPORT): // Not supported: >= v5.6
+ case errors.Is(err, syscall.EINVAL): // Not supported: < v5.6
+ case err == nil: // Supported and no error
+ poll.CloseFunc(s)
+ fallthrough
+ default:
+ // another error: MPTCP was not available but it might be later
+ mptcpAvailable = true
+ }
+
+ major, minor := unix.KernelVersion()
+ // SOL_MPTCP only supported from kernel 5.16
+ hasSOLMPTCP = major > 5 || (major == 5 && minor >= 16)
+}
+
+func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ if supportsMultipathTCP() {
+ if conn, err := sd.doDialTCPProto(ctx, laddr, raddr, _IPPROTO_MPTCP); err == nil {
+ return conn, nil
+ }
+ }
+
+ // Fallback to dialTCP if Multipath TCP isn't supported on this operating
+ // system. But also fallback in case of any error with MPTCP.
+ //
+ // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0)
+ // But just in case MPTCP is blocked differently (SELinux, etc.), just
+ // retry with "plain" TCP.
+ return sd.dialTCP(ctx, laddr, raddr)
+}
+
+func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
+ if supportsMultipathTCP() {
+ if dial, err := sl.listenTCPProto(ctx, laddr, _IPPROTO_MPTCP); err == nil {
+ return dial, nil
+ }
+ }
+
+ // Fallback to listenTCP if Multipath TCP isn't supported on this operating
+ // system. But also fallback in case of any error with MPTCP.
+ //
+ // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0)
+ // But just in case MPTCP is blocked differently (SELinux, etc.), just
+ // retry with "plain" TCP.
+ return sl.listenTCP(ctx, laddr)
+}
+
+// hasFallenBack reports whether the MPTCP connection has fallen back to "plain"
+// TCP.
+//
+// A connection can fallback to TCP for different reasons, e.g. the other peer
+// doesn't support it, a middle box "accidentally" drops the option, etc.
+//
+// If the MPTCP protocol has not been requested when creating the socket, this
+// method will return true: MPTCP is not being used.
+//
+// Kernel >= 5.16 returns EOPNOTSUPP/ENOPROTOOPT in case of fallback.
+// Older kernels will always return them even if MPTCP is used: not usable.
+func hasFallenBack(fd *netFD) bool {
+ _, err := fd.pfd.GetsockoptInt(_SOL_MPTCP, _MPTCP_INFO)
+
+ // 2 expected errors in case of fallback depending on the address family
+ // - AF_INET: EOPNOTSUPP
+ // - AF_INET6: ENOPROTOOPT
+ return err == syscall.EOPNOTSUPP || err == syscall.ENOPROTOOPT
+}
+
+// isUsingMPTCPProto reports whether the socket protocol is MPTCP.
+//
+// Compared to hasFallenBack method, here only the socket protocol being used is
+// checked: it can be MPTCP but it doesn't mean MPTCP is used on the wire, maybe
+// a fallback to TCP has been done.
+func isUsingMPTCPProto(fd *netFD) bool {
+ proto, _ := fd.pfd.GetsockoptInt(syscall.SOL_SOCKET, syscall.SO_PROTOCOL)
+
+ return proto == _IPPROTO_MPTCP
+}
+
+// isUsingMultipathTCP reports whether MPTCP is still being used.
+//
+// Please look at the description of hasFallenBack (kernel >=5.16) and
+// isUsingMPTCPProto methods for more details about what is being checked here.
+func isUsingMultipathTCP(fd *netFD) bool {
+ if hasSOLMPTCP {
+ return !hasFallenBack(fd)
+ }
+
+ return isUsingMPTCPProto(fd)
+}