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