summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/next/59166.txt1
-rw-r--r--src/net/mptcpsock_linux.go51
-rw-r--r--src/net/mptcpsock_linux_test.go37
-rw-r--r--src/net/mptcpsock_stub.go4
-rw-r--r--src/net/tcpsock.go16
5 files changed, 106 insertions, 3 deletions
diff --git a/api/next/59166.txt b/api/next/59166.txt
new file mode 100644
index 0000000000..2a620831e8
--- /dev/null
+++ b/api/next/59166.txt
@@ -0,0 +1 @@
+pkg net, method (*TCPConn) MultipathTCP() (bool, error) #59166
diff --git a/src/net/mptcpsock_linux.go b/src/net/mptcpsock_linux.go
index 15a7882498..e1a78fd59f 100644
--- a/src/net/mptcpsock_linux.go
+++ b/src/net/mptcpsock_linux.go
@@ -8,6 +8,7 @@ import (
"context"
"errors"
"internal/poll"
+ "internal/syscall/unix"
"sync"
"syscall"
)
@@ -15,11 +16,14 @@ import (
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 {
@@ -41,6 +45,10 @@ func initMPTCPavailable() {
// 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) {
@@ -74,3 +82,46 @@ func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPLis
// 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)
+}
diff --git a/src/net/mptcpsock_linux_test.go b/src/net/mptcpsock_linux_test.go
index 11543b0c8c..bf8fc951c5 100644
--- a/src/net/mptcpsock_linux_test.go
+++ b/src/net/mptcpsock_linux_test.go
@@ -40,11 +40,28 @@ func postAcceptMPTCP(ls *localServer, ch chan<- error) {
c := ls.cl[0]
- _, ok := c.(*TCPConn)
+ tcp, ok := c.(*TCPConn)
if !ok {
ch <- errors.New("struct is not a TCPConn")
return
}
+
+ mptcp, err := tcp.MultipathTCP()
+ if err != nil {
+ ch <- err
+ return
+ }
+
+ if !mptcp {
+ ch <- errors.New("incoming connection is not with MPTCP")
+ return
+ }
+
+ // Also check the method for the older kernels if not tested before
+ if hasSOLMPTCP && !isUsingMPTCPProto(tcp.fd) {
+ ch <- errors.New("incoming connection is not an MPTCP proto")
+ return
+ }
}
func dialerMPTCP(t *testing.T, addr string) {
@@ -64,7 +81,7 @@ func dialerMPTCP(t *testing.T, addr string) {
}
defer c.Close()
- _, ok := c.(*TCPConn)
+ tcp, ok := c.(*TCPConn)
if !ok {
t.Fatal("struct is not a TCPConn")
}
@@ -82,7 +99,21 @@ func dialerMPTCP(t *testing.T, addr string) {
t.Errorf("sent bytes (%s) are different from received ones (%s)", snt, b)
}
- t.Logf("outgoing connection from %s with mptcp", addr)
+ mptcp, err := tcp.MultipathTCP()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("outgoing connection from %s with mptcp: %t", addr, mptcp)
+
+ if !mptcp {
+ t.Error("outgoing connection is not with MPTCP")
+ }
+
+ // Also check the method for the older kernels if not tested before
+ if hasSOLMPTCP && !isUsingMPTCPProto(tcp.fd) {
+ t.Error("outgoing connection is not an MPTCP proto")
+ }
}
func canCreateMPTCPSocket() bool {
diff --git a/src/net/mptcpsock_stub.go b/src/net/mptcpsock_stub.go
index ae06772896..458c1530d7 100644
--- a/src/net/mptcpsock_stub.go
+++ b/src/net/mptcpsock_stub.go
@@ -17,3 +17,7 @@ func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCP
func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
return sl.listenTCP(ctx, laddr)
}
+
+func isUsingMultipathTCP(fd *netFD) bool {
+ return false
+}
diff --git a/src/net/tcpsock.go b/src/net/tcpsock.go
index f736f5a878..358e48723b 100644
--- a/src/net/tcpsock.go
+++ b/src/net/tcpsock.go
@@ -219,6 +219,22 @@ func (c *TCPConn) SetNoDelay(noDelay bool) error {
return nil
}
+// MultipathTCP reports whether the ongoing connection is using MPTCP.
+//
+// If Multipath TCP is not supported by the host, by the other peer or
+// intentionally / accidentally filtered out by a device in between, a
+// fallback to TCP will be done. This method does its best to check if
+// MPTCP is still being used or not.
+//
+// On Linux, more conditions are verified on kernels >= v5.16, improving
+// the results.
+func (c *TCPConn) MultipathTCP() (bool, error) {
+ if !c.ok() {
+ return false, syscall.EINVAL
+ }
+ return isUsingMultipathTCP(c.fd), nil
+}
+
func newTCPConn(fd *netFD, keepAlive time.Duration, keepAliveHook func(time.Duration)) *TCPConn {
setNoDelay(fd, true)
if keepAlive == 0 {