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 // SOL_MPTCP only supported from kernel 5.16. 57 hasSOLMPTCP = unix.KernelVersionGE(5, 16) 58 } 59 60 func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) { 61 if supportsMultipathTCP() { 62 if conn, err := sd.doDialTCPProto(ctx, laddr, raddr, _IPPROTO_MPTCP); err == nil { 63 return conn, nil 64 } 65 } 66 67 // Fallback to dialTCP if Multipath TCP isn't supported on this operating 68 // system. But also fallback in case of any error with MPTCP. 69 // 70 // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0) 71 // But just in case MPTCP is blocked differently (SELinux, etc.), just 72 // retry with "plain" TCP. 73 return sd.dialTCP(ctx, laddr, raddr) 74 } 75 76 func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) { 77 if supportsMultipathTCP() { 78 if dial, err := sl.listenTCPProto(ctx, laddr, _IPPROTO_MPTCP); err == nil { 79 return dial, nil 80 } 81 } 82 83 // Fallback to listenTCP if Multipath TCP isn't supported on this operating 84 // system. But also fallback in case of any error with MPTCP. 85 // 86 // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0) 87 // But just in case MPTCP is blocked differently (SELinux, etc.), just 88 // retry with "plain" TCP. 89 return sl.listenTCP(ctx, laddr) 90 } 91 92 // hasFallenBack reports whether the MPTCP connection has fallen back to "plain" 93 // TCP. 94 // 95 // A connection can fallback to TCP for different reasons, e.g. the other peer 96 // doesn't support it, a middle box "accidentally" drops the option, etc. 97 // 98 // If the MPTCP protocol has not been requested when creating the socket, this 99 // method will return true: MPTCP is not being used. 100 // 101 // Kernel >= 5.16 returns EOPNOTSUPP/ENOPROTOOPT in case of fallback. 102 // Older kernels will always return them even if MPTCP is used: not usable. 103 func hasFallenBack(fd *netFD) bool { 104 _, err := fd.pfd.GetsockoptInt(_SOL_MPTCP, _MPTCP_INFO) 105 106 // 2 expected errors in case of fallback depending on the address family 107 // - AF_INET: EOPNOTSUPP 108 // - AF_INET6: ENOPROTOOPT 109 return err == syscall.EOPNOTSUPP || err == syscall.ENOPROTOOPT 110 } 111 112 // isUsingMPTCPProto reports whether the socket protocol is MPTCP. 113 // 114 // Compared to hasFallenBack method, here only the socket protocol being used is 115 // checked: it can be MPTCP but it doesn't mean MPTCP is used on the wire, maybe 116 // a fallback to TCP has been done. 117 func isUsingMPTCPProto(fd *netFD) bool { 118 proto, _ := fd.pfd.GetsockoptInt(syscall.SOL_SOCKET, syscall.SO_PROTOCOL) 119 120 return proto == _IPPROTO_MPTCP 121 } 122 123 // isUsingMultipathTCP reports whether MPTCP is still being used. 124 // 125 // Please look at the description of hasFallenBack (kernel >=5.16) and 126 // isUsingMPTCPProto methods for more details about what is being checked here. 127 func isUsingMultipathTCP(fd *netFD) bool { 128 if !supportsMultipathTCP() { 129 return false 130 } 131 132 if hasSOLMPTCP { 133 return !hasFallenBack(fd) 134 } 135 136 return isUsingMPTCPProto(fd) 137 } 138