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