blob: 5134aba75eabb5d36e168fa1c5733ae7a01f6695 [file] [log] [blame]
// 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)
}
}