mirror of https://github.com/golang/go.git
net: mptcp: add TCPConn's MultipathTCP checker
This new TCPConn method returns whether the connection is using MPTCP or if a fallback to TCP has been done, e.g. because the other peer doesn't support MPTCP. When working on the new E2E test linked to MPTCP (#56539), it looks like the user might need to know such info to be able to do some special actions (report, stop, etc.). This also improves the test to make sure MPTCP has been used as expected. Regarding the implementation, from kernel version 5.16, it is possible to use: getsockopt(..., SOL_MPTCP, MPTCP_INFO, ...) and check if EOPNOTSUPP (IPv4) or ENOPROTOOPT (IPv6) is returned. If it is, it means a fallback to TCP has been done. See this link for more details: https://github.com/multipath-tcp/mptcp_net-next/issues/294 Before v5.16, there is no other simple way, from the userspace, to check if the created socket did a fallback to TCP. Netlink requests could be done to try to find more details about a specific socket but that seems quite a heavy machinery. Instead, only the protocol is checked on older kernels. The E2E test has been modified to check that the MPTCP connection didn't do any fallback to TCP, explicitely validating the two methods (SO_PROTOCOL and MPTCP_INFO) if it is supported by the host. This work has been co-developed by Gregory Detal <gregory.detal@tessares.net> and Benjamin Hesmans <benjamin.hesmans@tessares.net>. Fixes #59166 Change-Id: I5a313207146f71c66c349aa8588a2525179dd8b8 Reviewed-on: https://go-review.googlesource.com/c/go/+/471140 Auto-Submit: Ian Lance Taylor <iant@google.com> Run-TryBot: Ian Lance Taylor <iant@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com>
This commit is contained in:
parent
5a34790d6d
commit
fd8acb5d4a
|
|
@ -0,0 +1 @@
|
||||||
|
pkg net, method (*TCPConn) MultipathTCP() (bool, error) #59166
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"internal/poll"
|
"internal/poll"
|
||||||
|
"internal/syscall/unix"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
@ -15,11 +16,14 @@ import (
|
||||||
var (
|
var (
|
||||||
mptcpOnce sync.Once
|
mptcpOnce sync.Once
|
||||||
mptcpAvailable bool
|
mptcpAvailable bool
|
||||||
|
hasSOLMPTCP bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// These constants aren't in the syscall package, which is frozen
|
// These constants aren't in the syscall package, which is frozen
|
||||||
const (
|
const (
|
||||||
_IPPROTO_MPTCP = 0x106
|
_IPPROTO_MPTCP = 0x106
|
||||||
|
_SOL_MPTCP = 0x11c
|
||||||
|
_MPTCP_INFO = 0x1
|
||||||
)
|
)
|
||||||
|
|
||||||
func supportsMultipathTCP() bool {
|
func supportsMultipathTCP() bool {
|
||||||
|
|
@ -41,6 +45,10 @@ func initMPTCPavailable() {
|
||||||
// another error: MPTCP was not available but it might be later
|
// another error: MPTCP was not available but it might be later
|
||||||
mptcpAvailable = true
|
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) {
|
func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
|
||||||
|
|
@ -74,3 +82,46 @@ func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPLis
|
||||||
// retry with "plain" TCP.
|
// retry with "plain" TCP.
|
||||||
return sl.listenTCP(ctx, laddr)
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,28 @@ func postAcceptMPTCP(ls *localServer, ch chan<- error) {
|
||||||
|
|
||||||
c := ls.cl[0]
|
c := ls.cl[0]
|
||||||
|
|
||||||
_, ok := c.(*TCPConn)
|
tcp, ok := c.(*TCPConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
ch <- errors.New("struct is not a TCPConn")
|
ch <- errors.New("struct is not a TCPConn")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mptcp, err := tcp.MultipathTCP()
|
||||||
|
if err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mptcp {
|
||||||
|
ch <- errors.New("incoming connection is not with MPTCP")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check the method for the older kernels if not tested before
|
||||||
|
if hasSOLMPTCP && !isUsingMPTCPProto(tcp.fd) {
|
||||||
|
ch <- errors.New("incoming connection is not an MPTCP proto")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialerMPTCP(t *testing.T, addr string) {
|
func dialerMPTCP(t *testing.T, addr string) {
|
||||||
|
|
@ -64,7 +81,7 @@ func dialerMPTCP(t *testing.T, addr string) {
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
_, ok := c.(*TCPConn)
|
tcp, ok := c.(*TCPConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("struct is not a TCPConn")
|
t.Fatal("struct is not a TCPConn")
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +99,21 @@ func dialerMPTCP(t *testing.T, addr string) {
|
||||||
t.Errorf("sent bytes (%s) are different from received ones (%s)", snt, b)
|
t.Errorf("sent bytes (%s) are different from received ones (%s)", snt, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("outgoing connection from %s with mptcp", addr)
|
mptcp, err := tcp.MultipathTCP()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("outgoing connection from %s with mptcp: %t", addr, mptcp)
|
||||||
|
|
||||||
|
if !mptcp {
|
||||||
|
t.Error("outgoing connection is not with MPTCP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check the method for the older kernels if not tested before
|
||||||
|
if hasSOLMPTCP && !isUsingMPTCPProto(tcp.fd) {
|
||||||
|
t.Error("outgoing connection is not an MPTCP proto")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateMPTCPSocket() bool {
|
func canCreateMPTCPSocket() bool {
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,7 @@ func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCP
|
||||||
func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
|
func (sl *sysListener) listenMPTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
|
||||||
return sl.listenTCP(ctx, laddr)
|
return sl.listenTCP(ctx, laddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUsingMultipathTCP(fd *netFD) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,22 @@ func (c *TCPConn) SetNoDelay(noDelay bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MultipathTCP reports whether the ongoing connection is using MPTCP.
|
||||||
|
//
|
||||||
|
// If Multipath TCP is not supported by the host, by the other peer or
|
||||||
|
// intentionally / accidentally filtered out by a device in between, a
|
||||||
|
// fallback to TCP will be done. This method does its best to check if
|
||||||
|
// MPTCP is still being used or not.
|
||||||
|
//
|
||||||
|
// On Linux, more conditions are verified on kernels >= v5.16, improving
|
||||||
|
// the results.
|
||||||
|
func (c *TCPConn) MultipathTCP() (bool, error) {
|
||||||
|
if !c.ok() {
|
||||||
|
return false, syscall.EINVAL
|
||||||
|
}
|
||||||
|
return isUsingMultipathTCP(c.fd), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newTCPConn(fd *netFD, keepAlive time.Duration, keepAliveHook func(time.Duration)) *TCPConn {
|
func newTCPConn(fd *netFD, keepAlive time.Duration, keepAliveHook func(time.Duration)) *TCPConn {
|
||||||
setNoDelay(fd, true)
|
setNoDelay(fd, true)
|
||||||
if keepAlive == 0 {
|
if keepAlive == 0 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue