// 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 ( "bytes" "context" "errors" "syscall" "testing" ) func newLocalListenerMPTCP(t *testing.T, envVar bool) Listener { lc := &ListenConfig{} if envVar { if !lc.MultipathTCP() { t.Fatal("MultipathTCP Listen is not on despite GODEBUG=multipathtcp=1") } } else { if lc.MultipathTCP() { t.Error("MultipathTCP should be off by default") } lc.SetMultipathTCP(true) if !lc.MultipathTCP() { t.Fatal("MultipathTCP is not on after having been forced to on") } } ln, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } return ln } func postAcceptMPTCP(ls *localServer, ch chan<- error) { defer close(ch) if len(ls.cl) == 0 { ch <- errors.New("no accepted stream") return } c := ls.cl[0] tcp, ok := c.(*TCPConn) if !ok { ch <- errors.New("struct is not a TCPConn") 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, envVar bool) { d := &Dialer{} if envVar { if !d.MultipathTCP() { t.Fatal("MultipathTCP Dialer is not on despite GODEBUG=multipathtcp=1") } } else { if d.MultipathTCP() { t.Error("MultipathTCP should be off by default") } d.SetMultipathTCP(true) if !d.MultipathTCP() { t.Fatal("MultipathTCP is not on after having been forced to on") } } c, err := d.Dial("tcp", addr) if err != nil { t.Fatal(err) } defer c.Close() tcp, ok := c.(*TCPConn) if !ok { t.Fatal("struct is not a TCPConn") } // Transfer a bit of data to make sure everything is still OK snt := []byte("MPTCP TEST") if _, err := c.Write(snt); err != nil { t.Fatal(err) } b := make([]byte, len(snt)) if _, err := c.Read(b); err != nil { t.Fatal(err) } if !bytes.Equal(snt, b) { t.Errorf("sent bytes (%s) are different from received ones (%s)", snt, b) } 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 { // We want to know if we can create an MPTCP socket, not just if it is // available (mptcpAvailable()): it could be blocked by the admin fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, _IPPROTO_MPTCP) if err != nil { return false } syscall.Close(fd) return true } func testMultiPathTCP(t *testing.T, envVar bool) { if envVar { t.Log("Test with GODEBUG=multipathtcp=1") t.Setenv("GODEBUG", "multipathtcp=1") } else { t.Log("Test with GODEBUG=multipathtcp=0") t.Setenv("GODEBUG", "multipathtcp=0") } ln := newLocalListenerMPTCP(t, envVar) // similar to tcpsock_test:TestIPv6LinkLocalUnicastTCP ls := (&streamListener{Listener: ln}).newLocalServer() defer ls.teardown() if g, w := ls.Listener.Addr().Network(), "tcp"; g != w { t.Fatalf("Network type mismatch: got %q, want %q", g, w) } genericCh := make(chan error) mptcpCh := make(chan error) handler := func(ls *localServer, ln Listener) { ls.transponder(ln, genericCh) postAcceptMPTCP(ls, mptcpCh) } if err := ls.buildup(handler); err != nil { t.Fatal(err) } dialerMPTCP(t, ln.Addr().String(), envVar) if err := <-genericCh; err != nil { t.Error(err) } if err := <-mptcpCh; err != nil { t.Error(err) } } func TestMultiPathTCP(t *testing.T) { if !canCreateMPTCPSocket() { t.Skip("Cannot create MPTCP sockets") } for _, envVar := range []bool{false, true} { testMultiPathTCP(t, envVar) } }