diff options
Diffstat (limited to 'test/packetimpact')
-rw-r--r-- | test/packetimpact/runner/dut.go | 5 | ||||
-rw-r--r-- | test/packetimpact/testbench/dut.go | 4 | ||||
-rw-r--r-- | test/packetimpact/testbench/layers.go | 2 | ||||
-rw-r--r-- | test/packetimpact/testbench/rawsockets.go | 20 | ||||
-rw-r--r-- | test/packetimpact/testbench/testbench.go | 1 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 1 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_syncookie_test.go | 143 |
7 files changed, 132 insertions, 44 deletions
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 4fb2f5c4b..02678a76a 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -363,6 +363,9 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co // and receives packets and also sends POSIX socket commands to the // posix_server to be executed on the DUT. testArgs := []string{containerTestbenchBinary} + if testing.Verbose() { + testArgs = append(testArgs, "-test.v") + } testArgs = append(testArgs, extraTestArgs...) testArgs = append(testArgs, fmt.Sprintf("--native=%t", native), @@ -395,6 +398,8 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co } else if expectFailure { t.Logf(`test failed as expected: %v %s`, err, testLogs) + } else if testing.Verbose() { + t.Log(testLogs) } } diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 269e163bb..0cac0bf1b 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -498,8 +498,8 @@ func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Durat if readyFd := pfds[0].Fd; readyFd != fd { t.Fatalf("Poll returned an fd %d that was not requested (%d)", readyFd, fd) } - if got, want := pfds[0].Revents, int16(events); got&want == 0 { - t.Fatalf("Poll returned no events in our interest, got: %#b, want: %#b", got, want) + if got, want := pfds[0].Revents, int16(events); got&want != want { + t.Fatalf("Poll returned events does not include all of the interested events, got: %#b, want: %#b", got, want) } } diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go index 078f500e4..2644b3248 100644 --- a/test/packetimpact/testbench/layers.go +++ b/test/packetimpact/testbench/layers.go @@ -357,7 +357,7 @@ func (l *IPv4) ToBytes() ([]byte, error) { case *ICMPv4: fields.Protocol = uint8(header.ICMPv4ProtocolNumber) default: - // TODO(b/150301488): Support more protocols as needed. + // We can add support for more protocols as needed. return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n) } } diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go index feeb0888a..6d95c033d 100644 --- a/test/packetimpact/testbench/rawsockets.go +++ b/test/packetimpact/testbench/rawsockets.go @@ -17,7 +17,6 @@ package testbench import ( "encoding/binary" "fmt" - "math" "net" "testing" "time" @@ -81,19 +80,20 @@ func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte { deadline := time.Now().Add(timeout) for { - timeout = deadline.Sub(time.Now()) + timeout = time.Until(deadline) if timeout <= 0 { return nil } - whole, frac := math.Modf(timeout.Seconds()) - tv := unix.Timeval{ - Sec: int64(whole), - Usec: int64(frac * float64(time.Second/time.Microsecond)), + usec := timeout.Microseconds() + if usec == 0 { + // Timeout is less than a microsecond; set usec to 1 to avoid + // blocking indefinitely. + usec = 1 } - // The following should never happen, but having this guard here is better - // than blocking indefinitely in the future. - if tv.Sec == 0 && tv.Usec == 0 { - t.Fatal("setting SO_RCVTIMEO to 0 means blocking indefinitely") + const microsInOne = 1e6 + tv := unix.Timeval{ + Sec: usec / microsInOne, + Usec: usec % microsInOne, } if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil { t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err) diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go index d0efbcc08..caa389780 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -132,6 +132,7 @@ func registerFlags(fs *flag.FlagSet) { // Initialize initializes the testbench, it parse the flags and sets up the // pool of test networks for testbench's later use. func Initialize(fs *flag.FlagSet) { + testing.Init() registerFlags(fs) flag.Parse() if err := loadDUTInfos(); err != nil { diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 37b59b1d9..4cff0cf4c 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -384,6 +384,7 @@ packetimpact_testbench( deps = [ "//pkg/tcpip/header", "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/test/packetimpact/tests/tcp_syncookie_test.go b/test/packetimpact/tests/tcp_syncookie_test.go index 1c21c62ff..6be09996b 100644 --- a/test/packetimpact/tests/tcp_syncookie_test.go +++ b/test/packetimpact/tests/tcp_syncookie_test.go @@ -16,9 +16,12 @@ package tcp_syncookie_test import ( "flag" + "fmt" + "math" "testing" "time" + "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/test/packetimpact/testbench" @@ -28,43 +31,121 @@ func init() { testbench.Initialize(flag.CommandLine) } -// TestSynCookie test if the DUT listener is replying back using syn cookies. -// The test does not complete the handshake by not sending the ACK to SYNACK. -// When syncookies are not used, this forces the listener to retransmit SYNACK. -// And when syncookies are being used, there is no such retransmit. +// TestTCPSynCookie tests for ACK handling for connections in SYNRCVD state +// connections with and without syncookies. It verifies if the passive open +// connection is indeed using syncookies before proceeding. func TestTCPSynCookie(t *testing.T) { dut := testbench.NewDUT(t) + for _, tt := range []struct { + accept bool + flags header.TCPFlags + }{ + {accept: true, flags: header.TCPFlagAck}, + {accept: true, flags: header.TCPFlagAck | header.TCPFlagPsh}, + {accept: false, flags: header.TCPFlagAck | header.TCPFlagSyn}, + {accept: true, flags: header.TCPFlagAck | header.TCPFlagFin}, + {accept: false, flags: header.TCPFlagAck | header.TCPFlagRst}, + {accept: false, flags: header.TCPFlagRst}, + } { + t.Run(fmt.Sprintf("flags=%s", tt.flags), func(t *testing.T) { + // Make a copy before parallelizing the test and refer to that + // within the test. Otherwise, the test reference could be pointing + // to an incorrect variant based on how it is scheduled. + test := tt - // Listening endpoint accepts one more connection than the listen backlog. - _, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) + t.Parallel() - var withoutSynCookieConn testbench.TCPIPv4 - var withSynCookieConn testbench.TCPIPv4 + // Listening endpoint accepts one more connection than the listen + // backlog. Listener starts using syncookies when it sees a new SYN + // and has backlog size of connections in SYNRCVD state. Keep the + // listen backlog 1, so that the test can define 2 connections + // without and with using syncookies. + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) + defer dut.Close(t, listenFD) - // Test if the DUT listener replies to more SYNs than listen backlog+1 - for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} { - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - } - defer withoutSynCookieConn.Close(t) - defer withSynCookieConn.Close(t) + var withoutSynCookieConn testbench.TCPIPv4 + var withSynCookieConn testbench.TCPIPv4 - checkSynAck := func(t *testing.T, conn *testbench.TCPIPv4, expectRetransmit bool) { - // Expect dut connection to have transitioned to SYN-RCVD state. - conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK, but got %s", err) - } + for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} { + *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + } + defer withoutSynCookieConn.Close(t) + defer withSynCookieConn.Close(t) - // If the DUT listener is using syn cookies, it will not retransmit SYNACK - got, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second) - if expectRetransmit && err != nil { - t.Fatalf("expected retransmitted SYN-ACK, but got %s", err) - } - if !expectRetransmit && err == nil { - t.Fatalf("expected no retransmitted SYN-ACK, but got %s", got) - } - } + // Setup the 2 connections in SYNRCVD state and verify if one of the + // connection is indeed using syncookies by checking for absence of + // SYNACK retransmits. + for _, c := range []struct { + desc string + conn *testbench.TCPIPv4 + expectRetransmit bool + }{ + {desc: "without syncookies", conn: &withoutSynCookieConn, expectRetransmit: true}, + {desc: "with syncookies", conn: &withSynCookieConn, expectRetransmit: false}, + } { + t.Run(c.desc, func(t *testing.T) { + // Expect dut connection to have transitioned to SYNRCVD state. + c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if _, err := c.conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected SYNACK, but got %s", err) + } + + // If the DUT listener is using syn cookies, it will not retransmit SYNACK. + 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) + if c.expectRetransmit && err != nil { + t.Fatalf("expected retransmitted SYNACK, but got %s", err) + } + if !c.expectRetransmit && err == nil { + t.Fatalf("expected no retransmitted SYNACK, but got %s", got) + } + }) + } - t.Run("without syncookies", func(t *testing.T) { checkSynAck(t, &withoutSynCookieConn, true /*expectRetransmit*/) }) - t.Run("with syncookies", func(t *testing.T) { checkSynAck(t, &withSynCookieConn, false /*expectRetransmit*/) }) + // Check whether ACKs with the given flags completes the handshake. + for _, c := range []struct { + desc string + conn *testbench.TCPIPv4 + }{ + {desc: "with syncookies", conn: &withSynCookieConn}, + {desc: "without syncookies", conn: &withoutSynCookieConn}, + } { + t.Run(c.desc, func(t *testing.T) { + pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: math.MaxInt16}}, 0 /*timeout*/) + if got, want := len(pfds), 0; got != want { + t.Fatalf("dut.Poll(...) = %d, want = %d", got, want) + } + + sampleData := []byte("Sample Data") + samplePayload := &testbench.Payload{Bytes: sampleData} + + c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)}, samplePayload) + pfds = dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: unix.POLLIN}}, time.Second) + want := 0 + if test.accept { + want = 1 + } + if got := len(pfds); got != want { + t.Fatalf("got dut.Poll(...) = %d, want = %d", got, want) + } + // Accept the connection to enable poll on any subsequent connection. + if test.accept { + fd, _ := dut.Accept(t, listenFD) + if test.flags.Contains(header.TCPFlagFin) { + if dut.Uname.IsLinux() { + dut.PollOne(t, fd, unix.POLLIN|unix.POLLRDHUP, time.Second) + } else { + // TODO(gvisor.dev/issue/6015): Notify POLLIN|POLLRDHUP on incoming FIN. + dut.PollOne(t, fd, unix.POLLIN, time.Second) + } + } + got := dut.Recv(t, fd, int32(len(sampleData)), 0) + if diff := cmp.Diff(got, sampleData); diff != "" { + t.Fatalf("dut.Recv: data mismatch (-want +got):\n%s", diff) + } + dut.Close(t, fd) + } + }) + } + }) + } } |