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 }