// 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) }