gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/tcp_syncookie_test.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tcp_syncookie_test
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"math"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"golang.org/x/sys/unix"
    26  	"gvisor.dev/gvisor/pkg/tcpip/header"
    27  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    28  )
    29  
    30  func init() {
    31  	testbench.Initialize(flag.CommandLine)
    32  }
    33  
    34  // TestTCPSynCookie tests for ACK handling for connections in SYNRCVD state
    35  // connections with and without syncookies. It verifies if the passive open
    36  // connection is indeed using syncookies before proceeding.
    37  func TestTCPSynCookie(t *testing.T) {
    38  	dut := testbench.NewDUT(t)
    39  	for _, tt := range []struct {
    40  		accept bool
    41  		flags  header.TCPFlags
    42  	}{
    43  		{accept: true, flags: header.TCPFlagAck},
    44  		{accept: true, flags: header.TCPFlagAck | header.TCPFlagPsh},
    45  		{accept: false, flags: header.TCPFlagAck | header.TCPFlagSyn},
    46  		{accept: true, flags: header.TCPFlagAck | header.TCPFlagFin},
    47  		{accept: false, flags: header.TCPFlagAck | header.TCPFlagRst},
    48  		{accept: false, flags: header.TCPFlagRst},
    49  	} {
    50  		t.Run(fmt.Sprintf("flags=%s", tt.flags), func(t *testing.T) {
    51  			// Make a copy before parallelizing the test and refer to that
    52  			// within the test. Otherwise, the test reference could be pointing
    53  			// to an incorrect variant based on how it is scheduled.
    54  			test := tt
    55  
    56  			t.Parallel()
    57  
    58  			// Listening endpoint accepts one more connection than the listen
    59  			// backlog. Listener starts using syncookies when it sees a new SYN
    60  			// and has backlog size of connections in SYNRCVD state. Keep the
    61  			// listen backlog 1, so that the test can define 2 connections
    62  			// without and with using syncookies.
    63  			listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
    64  			defer dut.Close(t, listenFD)
    65  
    66  			var withoutSynCookieConn testbench.TCPIPv4
    67  			var withSynCookieConn testbench.TCPIPv4
    68  
    69  			for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} {
    70  				*conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
    71  			}
    72  			defer withoutSynCookieConn.Close(t)
    73  			defer withSynCookieConn.Close(t)
    74  
    75  			// Setup the 2 connections in SYNRCVD state and verify if one of the
    76  			// connection is indeed using syncookies by checking for absence of
    77  			// SYNACK retransmits.
    78  			for _, c := range []struct {
    79  				desc             string
    80  				conn             *testbench.TCPIPv4
    81  				expectRetransmit bool
    82  			}{
    83  				{desc: "without syncookies", conn: &withoutSynCookieConn, expectRetransmit: true},
    84  				{desc: "with syncookies", conn: &withSynCookieConn, expectRetransmit: false},
    85  			} {
    86  				t.Run(c.desc, func(t *testing.T) {
    87  					// Expect dut connection to have transitioned to SYNRCVD state.
    88  					c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
    89  					if _, err := c.conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
    90  						t.Fatalf("expected SYNACK, but got %s", err)
    91  					}
    92  
    93  					// If the DUT listener is using syn cookies, it will not retransmit SYNACK.
    94  					got, err := c.conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*c.conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second)
    95  					if c.expectRetransmit && err != nil {
    96  						t.Fatalf("expected retransmitted SYNACK, but got %s", err)
    97  					}
    98  					if !c.expectRetransmit && err == nil {
    99  						t.Fatalf("expected no retransmitted SYNACK, but got %s", got)
   100  					}
   101  				})
   102  			}
   103  
   104  			// Check whether ACKs with the given flags completes the handshake.
   105  			for _, c := range []struct {
   106  				desc string
   107  				conn *testbench.TCPIPv4
   108  			}{
   109  				{desc: "with syncookies", conn: &withSynCookieConn},
   110  				{desc: "without syncookies", conn: &withoutSynCookieConn},
   111  			} {
   112  				t.Run(c.desc, func(t *testing.T) {
   113  					pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: math.MaxInt16}}, 0 /*timeout*/)
   114  					if got, want := len(pfds), 0; got != want {
   115  						t.Fatalf("dut.Poll(...) = %d, want = %d", got, want)
   116  					}
   117  
   118  					sampleData := []byte("Sample Data")
   119  					samplePayload := &testbench.Payload{Bytes: sampleData}
   120  
   121  					c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)}, samplePayload)
   122  					pfds = dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: unix.POLLIN}}, time.Second)
   123  					want := 0
   124  					if test.accept {
   125  						want = 1
   126  					}
   127  					if got := len(pfds); got != want {
   128  						t.Fatalf("got dut.Poll(...) = %d, want = %d", got, want)
   129  					}
   130  					// Accept the connection to enable poll on any subsequent connection.
   131  					if test.accept {
   132  						fd, _ := dut.Accept(t, listenFD)
   133  						if test.flags.Contains(header.TCPFlagFin) {
   134  							dut.PollOne(t, fd, unix.POLLIN|unix.POLLRDHUP, time.Second)
   135  						}
   136  						got := dut.Recv(t, fd, int32(len(sampleData)), 0)
   137  						if diff := cmp.Diff(got, sampleData); diff != "" {
   138  							t.Fatalf("dut.Recv: data mismatch (-want +got):\n%s", diff)
   139  						}
   140  						dut.Close(t, fd)
   141  					}
   142  				})
   143  			}
   144  		})
   145  	}
   146  }