mirror of https://go.googlesource.com/go
128 lines
4.0 KiB
Go
128 lines
4.0 KiB
Go
// 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)
|
|
}
|