summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--pkg/tcpip/transport/tcp/accept.go7
-rw-r--r--pkg/tcpip/transport/tcp/tcp_test.go48
-rw-r--r--test/packetimpact/tests/tcp_syncookie_test.go127
3 files changed, 150 insertions, 32 deletions
diff --git a/pkg/tcpip/transport/tcp/accept.go b/pkg/tcpip/transport/tcp/accept.go
index 2c65b737d..2b5abd3ee 100644
--- a/pkg/tcpip/transport/tcp/accept.go
+++ b/pkg/tcpip/transport/tcp/accept.go
@@ -560,6 +560,10 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
}
switch {
+ case s.flags.Contains(header.TCPFlagRst):
+ e.stack.Stats().DroppedPackets.Increment()
+ return nil
+
case s.flags == header.TCPFlagSyn:
if e.acceptQueueIsFull() {
e.stack.Stats().TCP.ListenOverflowSynDrop.Increment()
@@ -611,7 +615,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
e.stack.Stats().TCP.ListenOverflowSynCookieSent.Increment()
return nil
- case (s.flags & header.TCPFlagAck) != 0:
+ case s.flags.Contains(header.TCPFlagAck):
if e.acceptQueueIsFull() {
// Silently drop the ack as the application can't accept
// the connection at this point. The ack will be
@@ -753,6 +757,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) tcpip.Err
return nil
default:
+ e.stack.Stats().DroppedPackets.Increment()
return nil
}
}
diff --git a/pkg/tcpip/transport/tcp/tcp_test.go b/pkg/tcpip/transport/tcp/tcp_test.go
index e7ede7662..9bbe9bc3e 100644
--- a/pkg/tcpip/transport/tcp/tcp_test.go
+++ b/pkg/tcpip/transport/tcp/tcp_test.go
@@ -6238,6 +6238,54 @@ func TestPassiveFailedConnectionAttemptIncrement(t *testing.T) {
}
}
+func TestListenDropIncrement(t *testing.T) {
+ c := context.New(t, defaultMTU)
+ defer c.Cleanup()
+
+ stats := c.Stack().Stats()
+ c.Create(-1 /*epRcvBuf*/)
+
+ if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != nil {
+ t.Fatalf("Bind failed: %s", err)
+ }
+ if err := c.EP.Listen(1 /*backlog*/); err != nil {
+ t.Fatalf("Listen failed: %s", err)
+ }
+
+ initialDropped := stats.DroppedPackets.Value()
+
+ // Send RST, FIN segments, that are expected to be dropped by the listener.
+ c.SendPacket(nil, &context.Headers{
+ SrcPort: context.TestPort,
+ DstPort: context.StackPort,
+ Flags: header.TCPFlagRst,
+ })
+ c.SendPacket(nil, &context.Headers{
+ SrcPort: context.TestPort,
+ DstPort: context.StackPort,
+ Flags: header.TCPFlagFin,
+ })
+
+ // To ensure that the RST, FIN sent earlier are indeed received and ignored
+ // by the listener, send a SYN and wait for the SYN to be ACKd.
+ irs := seqnum.Value(context.TestInitialSequenceNumber)
+ c.SendPacket(nil, &context.Headers{
+ SrcPort: context.TestPort,
+ DstPort: context.StackPort,
+ Flags: header.TCPFlagSyn,
+ SeqNum: irs,
+ })
+ checker.IPv4(t, c.GetPacket(), checker.TCP(checker.SrcPort(context.StackPort),
+ checker.DstPort(context.TestPort),
+ checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
+ checker.TCPAckNum(uint32(irs)+1),
+ ))
+
+ if got, want := stats.DroppedPackets.Value(), initialDropped+2; got != want {
+ t.Fatalf("got stats.DroppedPackets.Value() = %d, want = %d", got, want)
+ }
+}
+
func TestEndpointBindListenAcceptState(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
diff --git a/test/packetimpact/tests/tcp_syncookie_test.go b/test/packetimpact/tests/tcp_syncookie_test.go
index 1c21c62ff..1a016bd1a 100644
--- a/test/packetimpact/tests/tcp_syncookie_test.go
+++ b/test/packetimpact/tests/tcp_syncookie_test.go
@@ -16,6 +16,8 @@ package tcp_syncookie_test
import (
"flag"
+ "fmt"
+ "math"
"testing"
"time"
@@ -28,43 +30,106 @@ 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)
+ }
+
+ c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)})
+ 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)
+ dut.Close(t, fd)
+ }
+ })
+ }
+ })
+ }
}