Source file src/net/mptcpsock_linux.go

     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  		fallthrough
    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  

View as plain text