mptcpsock_linux.mx raw

   1  // Copyright 2023 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package net
   6  
   7  import (
   8  	"context"
   9  	"errors"
  10  	"internal/poll"
  11  	"internal/syscall/unix"
  12  	"sync"
  13  	"syscall"
  14  )
  15  
  16  var (
  17  	mptcpOnce      sync.Once
  18  	mptcpAvailable bool
  19  	hasSOLMPTCP    bool // only valid if mptcpAvailable is true
  20  )
  21  
  22  // These constants aren't in the syscall package, which is frozen
  23  const (
  24  	_IPPROTO_MPTCP = 0x106
  25  	_SOL_MPTCP     = 0x11c
  26  	_MPTCP_INFO    = 0x1
  27  )
  28  
  29  func supportsMultipathTCP() bool {
  30  	mptcpOnce.Do(initMPTCPavailable)
  31  	return mptcpAvailable
  32  }
  33  
  34  // Check that MPTCP is supported by attempting to create an MPTCP socket and by
  35  // looking at the returned error if any.
  36  func initMPTCPavailable() {
  37  	family := syscall.AF_INET
  38  	if !supportsIPv4() {
  39  		family = syscall.AF_INET6
  40  	}
  41  	s, err := sysSocket(family, syscall.SOCK_STREAM, _IPPROTO_MPTCP)
  42  
  43  	switch {
  44  	case errors.Is(err, syscall.EPROTONOSUPPORT): // Not supported: >= v5.6
  45  		return
  46  	case errors.Is(err, syscall.EINVAL): // Not supported: < v5.6
  47  		return
  48  	case err == nil: // Supported and no error
  49  		poll.CloseFunc(s)
  50  		mptcpAvailable = true
  51  	default:
  52  		// another error: MPTCP was not available but it might be later
  53  		mptcpAvailable = true
  54  	}
  55  
  56  	major, minor := unix.KernelVersion()
  57  	// SOL_MPTCP only supported from kernel 5.16
  58  	hasSOLMPTCP = major > 5 || (major == 5 && minor >= 16)
  59  }
  60  
  61  func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
  62  	if supportsMultipathTCP() {
  63  		if conn, err := sd.doDialTCPProto(ctx, laddr, raddr, _IPPROTO_MPTCP); err == nil {
  64  			return conn, nil
  65  		}
  66  	}
  67  
  68  	// Fallback to dialTCP if Multipath TCP isn't supported on this operating
  69  	// system. But also fallback in case of any error with MPTCP.
  70  	//
  71  	// Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0)
  72  	// But just in case MPTCP is blocked differently (SELinux, etc.), just
  73  	// retry with "plain" TCP.
  74  	return sd.dialTCP(ctx, laddr, raddr)
  75  }
  76  
  77  func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
  78  	if supportsMultipathTCP() {
  79  		if dial, err := sl.listenTCPProto(ctx, laddr, _IPPROTO_MPTCP); err == nil {
  80  			return dial, nil
  81  		}
  82  	}
  83  
  84  	// Fallback to listenTCP if Multipath TCP isn't supported on this operating
  85  	// system. But also fallback in case of any error with MPTCP.
  86  	//
  87  	// Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0)
  88  	// But just in case MPTCP is blocked differently (SELinux, etc.), just
  89  	// retry with "plain" TCP.
  90  	return sl.listenTCP(ctx, laddr)
  91  }
  92  
  93  // hasFallenBack reports whether the MPTCP connection has fallen back to "plain"
  94  // TCP.
  95  //
  96  // A connection can fallback to TCP for different reasons, e.g. the other peer
  97  // doesn't support it, a middle box "accidentally" drops the option, etc.
  98  //
  99  // If the MPTCP protocol has not been requested when creating the socket, this
 100  // method will return true: MPTCP is not being used.
 101  //
 102  // Kernel >= 5.16 returns EOPNOTSUPP/ENOPROTOOPT in case of fallback.
 103  // Older kernels will always return them even if MPTCP is used: not usable.
 104  func hasFallenBack(fd *netFD) bool {
 105  	_, err := fd.pfd.GetsockoptInt(_SOL_MPTCP, _MPTCP_INFO)
 106  
 107  	// 2 expected errors in case of fallback depending on the address family
 108  	//   - AF_INET:  EOPNOTSUPP
 109  	//   - AF_INET6: ENOPROTOOPT
 110  	return err == syscall.EOPNOTSUPP || err == syscall.ENOPROTOOPT
 111  }
 112  
 113  // isUsingMPTCPProto reports whether the socket protocol is MPTCP.
 114  //
 115  // Compared to hasFallenBack method, here only the socket protocol being used is
 116  // checked: it can be MPTCP but it doesn't mean MPTCP is used on the wire, maybe
 117  // a fallback to TCP has been done.
 118  func isUsingMPTCPProto(fd *netFD) bool {
 119  	proto, _ := fd.pfd.GetsockoptInt(syscall.SOL_SOCKET, syscall.SO_PROTOCOL)
 120  
 121  	return proto == _IPPROTO_MPTCP
 122  }
 123  
 124  // isUsingMultipathTCP reports whether MPTCP is still being used.
 125  //
 126  // Please look at the description of hasFallenBack (kernel >=5.16) and
 127  // isUsingMPTCPProto methods for more details about what is being checked here.
 128  func isUsingMultipathTCP(fd *netFD) bool {
 129  	if !supportsMultipathTCP() {
 130  		return false
 131  	}
 132  
 133  	if hasSOLMPTCP {
 134  		return !hasFallenBack(fd)
 135  	}
 136  
 137  	return isUsingMPTCPProto(fd)
 138  }
 139