mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Ignore RST received for a TCP listener
The current implementation has a bug where TCP listener does not ignore RSTs from the peer. While handling RST+ACK from the peer, this bug can complete handshakes that use syncookies. This results in half-open connection delivered to the accept queue. Fixes #6076 PiperOrigin-RevId: 376868749
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
var withoutSynCookieConn testbench.TCPIPv4
|
||||
var withSynCookieConn testbench.TCPIPv4
|
||||
|
||||
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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
defer withoutSynCookieConn.Close(t)
|
||||
defer withSynCookieConn.Close(t)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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*/) })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user