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
5package net
6
7import (
8	"context"
9	"errors"
10	"internal/poll"
11	"internal/syscall/unix"
12	"sync"
13	"syscall"
14)
15
16var (
17	mptcpOnce      sync.Once
18	mptcpAvailable bool
19	hasSOLMPTCP    bool
20)
21
22// These constants aren't in the syscall package, which is frozen
23const (
24	_IPPROTO_MPTCP = 0x106
25	_SOL_MPTCP     = 0x11c
26	_MPTCP_INFO    = 0x1
27)
28
29func 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.
36func 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
54func (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
70func (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.
97func 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.
111func 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.
121func isUsingMultipathTCP(fd *netFD) bool {
122	if hasSOLMPTCP {
123		return !hasFallenBack(fd)
124	}
125
126	return isUsingMPTCPProto(fd)
127}
128