| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package net |
| |
| import ( |
| "context" |
| "errors" |
| "internal/poll" |
| "internal/syscall/unix" |
| "sync" |
| "syscall" |
| ) |
| |
| var ( |
| mptcpOnce sync.Once |
| mptcpAvailable bool |
| hasSOLMPTCP bool |
| ) |
| |
| // These constants aren't in the syscall package, which is frozen |
| const ( |
| _IPPROTO_MPTCP = 0x106 |
| _SOL_MPTCP = 0x11c |
| _MPTCP_INFO = 0x1 |
| ) |
| |
| func supportsMultipathTCP() bool { |
| mptcpOnce.Do(initMPTCPavailable) |
| return mptcpAvailable |
| } |
| |
| // Check that MPTCP is supported by attempting to create an MPTCP socket and by |
| // looking at the returned error if any. |
| func initMPTCPavailable() { |
| s, err := sysSocket(syscall.AF_INET, syscall.SOCK_STREAM, _IPPROTO_MPTCP) |
| switch { |
| case errors.Is(err, syscall.EPROTONOSUPPORT): // Not supported: >= v5.6 |
| case errors.Is(err, syscall.EINVAL): // Not supported: < v5.6 |
| case err == nil: // Supported and no error |
| poll.CloseFunc(s) |
| fallthrough |
| default: |
| // another error: MPTCP was not available but it might be later |
| mptcpAvailable = true |
| } |
| |
| major, minor := unix.KernelVersion() |
| // SOL_MPTCP only supported from kernel 5.16 |
| hasSOLMPTCP = major > 5 || (major == 5 && minor >= 16) |
| } |
| |
| func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) { |
| if supportsMultipathTCP() { |
| if conn, err := sd.doDialTCPProto(ctx, laddr, raddr, _IPPROTO_MPTCP); err == nil { |
| return conn, nil |
| } |
| } |
| |
| // Fallback to dialTCP if Multipath TCP isn't supported on this operating |
| // system. But also fallback in case of any error with MPTCP. |
| // |
| // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0) |
| // But just in case MPTCP is blocked differently (SELinux, etc.), just |
| // retry with "plain" TCP. |
| return sd.dialTCP(ctx, laddr, raddr) |
| } |
| |
| func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) { |
| if supportsMultipathTCP() { |
| if dial, err := sl.listenTCPProto(ctx, laddr, _IPPROTO_MPTCP); err == nil { |
| return dial, nil |
| } |
| } |
| |
| // Fallback to listenTCP if Multipath TCP isn't supported on this operating |
| // system. But also fallback in case of any error with MPTCP. |
| // |
| // Possible MPTCP specific error: ENOPROTOOPT (sysctl net.mptcp.enabled=0) |
| // But just in case MPTCP is blocked differently (SELinux, etc.), just |
| // retry with "plain" TCP. |
| return sl.listenTCP(ctx, laddr) |
| } |
| |
| // hasFallenBack reports whether the MPTCP connection has fallen back to "plain" |
| // TCP. |
| // |
| // A connection can fallback to TCP for different reasons, e.g. the other peer |
| // doesn't support it, a middle box "accidentally" drops the option, etc. |
| // |
| // If the MPTCP protocol has not been requested when creating the socket, this |
| // method will return true: MPTCP is not being used. |
| // |
| // Kernel >= 5.16 returns EOPNOTSUPP/ENOPROTOOPT in case of fallback. |
| // Older kernels will always return them even if MPTCP is used: not usable. |
| func hasFallenBack(fd *netFD) bool { |
| _, err := fd.pfd.GetsockoptInt(_SOL_MPTCP, _MPTCP_INFO) |
| |
| // 2 expected errors in case of fallback depending on the address family |
| // - AF_INET: EOPNOTSUPP |
| // - AF_INET6: ENOPROTOOPT |
| return err == syscall.EOPNOTSUPP || err == syscall.ENOPROTOOPT |
| } |
| |
| // isUsingMPTCPProto reports whether the socket protocol is MPTCP. |
| // |
| // Compared to hasFallenBack method, here only the socket protocol being used is |
| // checked: it can be MPTCP but it doesn't mean MPTCP is used on the wire, maybe |
| // a fallback to TCP has been done. |
| func isUsingMPTCPProto(fd *netFD) bool { |
| proto, _ := fd.pfd.GetsockoptInt(syscall.SOL_SOCKET, syscall.SO_PROTOCOL) |
| |
| return proto == _IPPROTO_MPTCP |
| } |
| |
| // isUsingMultipathTCP reports whether MPTCP is still being used. |
| // |
| // Please look at the description of hasFallenBack (kernel >=5.16) and |
| // isUsingMPTCPProto methods for more details about what is being checked here. |
| func isUsingMultipathTCP(fd *netFD) bool { |
| if hasSOLMPTCP { |
| return !hasFallenBack(fd) |
| } |
| |
| return isUsingMPTCPProto(fd) |
| } |