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