mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Test syn and accept queue behavior on shutdown
Enhance the backlog test to exercise the syn queue. Updates #6671. PiperOrigin-RevId: 400094530
This commit is contained in:
committed by
gVisor bot
parent
9fff9469a2
commit
84f1578e9e
@@ -15,7 +15,9 @@
|
||||
package tcp_listen_backlog_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -35,60 +37,271 @@ func init() {
|
||||
func TestTCPListenBacklog(t *testing.T) {
|
||||
dut := testbench.NewDUT(t)
|
||||
|
||||
// Listening endpoint accepts one more connection than the listen backlog.
|
||||
listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 0 /*backlog*/)
|
||||
// This is the number of pending connections before SYN cookies are used.
|
||||
const backlog = 10
|
||||
|
||||
var establishedConn testbench.TCPIPv4
|
||||
var incompleteConn testbench.TCPIPv4
|
||||
listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, backlog)
|
||||
defer dut.Close(t, listenFd)
|
||||
|
||||
// Test if the DUT listener replies to more SYNs than listen backlog+1
|
||||
for i, conn := range []*testbench.TCPIPv4{&establishedConn, &incompleteConn} {
|
||||
*conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
|
||||
// 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 for %d connection, %s", i, err)
|
||||
// Fill the SYN queue with connections in SYN-RCVD. We will use these to test
|
||||
// that ACKs received while the accept queue is full are ignored.
|
||||
var synQueueConns [backlog]testbench.TCPIPv4
|
||||
defer func() {
|
||||
for i := range synQueueConns {
|
||||
synQueueConns[i].Close(t)
|
||||
}
|
||||
}()
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range synQueueConns {
|
||||
conn := &synQueueConns[i]
|
||||
*conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{})
|
||||
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
defer establishedConn.Close(t)
|
||||
defer incompleteConn.Close(t)
|
||||
|
||||
// Send the ACK to complete handshake.
|
||||
establishedConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
const payloadLen = 1
|
||||
payload := testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
|
||||
|
||||
// Poll for the established connection ready for accept.
|
||||
dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
|
||||
// Fill the accept queue with connections established using SYN cookies.
|
||||
var synCookieConns [backlog + 1]testbench.TCPIPv4
|
||||
defer func() {
|
||||
for i := range synCookieConns {
|
||||
synCookieConns[i].Close(t)
|
||||
}
|
||||
}()
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range synCookieConns {
|
||||
conn := &synCookieConns[i]
|
||||
*conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{})
|
||||
|
||||
// Send the ACK to complete handshake, expect this to be dropped by the
|
||||
// listener as the accept queue would be full because of the previous
|
||||
// handshake.
|
||||
incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
// Let the test wait for sometime so that the ACK is indeed dropped by
|
||||
// the listener. Without such a wait, the DUT accept can race with
|
||||
// ACK handling (dropping) causing the test to be flaky.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Drain the accept queue to enable poll for subsequent connections on the
|
||||
// listener.
|
||||
fd, _ := dut.Accept(t, listenFd)
|
||||
dut.Close(t, fd)
|
||||
|
||||
// The ACK for the incomplete connection should be ignored by the
|
||||
// listening endpoint and the poll on listener should now time out.
|
||||
if pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFd, Events: unix.POLLIN}}, time.Second); len(pfds) != 0 {
|
||||
t.Fatalf("got dut.Poll(...) = %#v", pfds)
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
// Send a payload so we can observe the dut ACK.
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &payload)
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Re-send the ACK to complete handshake and re-fill the accept-queue.
|
||||
incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
|
||||
// Send ACKs to complete the handshakes. These are expected to be dropped
|
||||
// because the accept queue is full.
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range synQueueConns {
|
||||
conn := &synQueueConns[i]
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Now initiate a new connection when the accept queue is full.
|
||||
connectingConn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
|
||||
defer connectingConn.Close(t)
|
||||
// Expect dut connection to drop the SYN and let the client stay in SYN_SENT state.
|
||||
connectingConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
|
||||
if got, err := connectingConn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err == nil {
|
||||
t.Fatalf("expected no SYN-ACK, but got %s", got)
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
// Wait for the SYN-ACK to be retransmitted to confirm the ACK was
|
||||
// dropped.
|
||||
seqNum := uint32(*conn.RemoteSeqNum(t) - 1)
|
||||
if got, err := conn.Expect(t, testbench.TCP{SeqNum: &seqNum}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func() {
|
||||
// Now initiate a new connection when the accept queue is full.
|
||||
connectingConn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{})
|
||||
defer connectingConn.Close(t)
|
||||
// Expect dut connection to drop the SYN.
|
||||
connectingConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
|
||||
if got, err := connectingConn.Expect(t, testbench.TCP{}, time.Second); err == nil {
|
||||
t.Fatalf("expected no TCP frame, got %s", got)
|
||||
}
|
||||
}()
|
||||
|
||||
// Drain the accept queue.
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range synCookieConns {
|
||||
conn := &synCookieConns[i]
|
||||
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
fd, _ := dut.Accept(t, listenFd)
|
||||
b := dut.Recv(t, fd, payloadLen+1, 0)
|
||||
dut.Close(t, fd)
|
||||
if !bytes.Equal(b, payload.Bytes) {
|
||||
t.Errorf("connection %d: got dut.Recv = %x, want = %x", i, b, payload.Bytes)
|
||||
}
|
||||
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagFin|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
|
||||
// Prevent retransmission.
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the partial connections to move them from the SYN queue to the
|
||||
// accept queue. We will use these to test that connections in the accept
|
||||
// queue are closed on listener shutdown.
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range synQueueConns {
|
||||
conn := &synQueueConns[i]
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
tcp := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}
|
||||
|
||||
// Exercise connections with and without pending data.
|
||||
if i%2 == 0 {
|
||||
// Send ACK with no payload; wait for absence of SYN-ACK retransmit.
|
||||
conn.Send(t, tcp)
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil {
|
||||
t.Errorf("%d: expected no TCP frame, got %s", i, got)
|
||||
}
|
||||
} else {
|
||||
// Send ACK with payload; wait for ACK.
|
||||
conn.Send(t, tcp, &payload)
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// The accept queue now has N-1 connections in it. The next incoming SYN will
|
||||
// enter the SYN queue, and the one following will use SYN cookies. We test
|
||||
// both.
|
||||
var connectingConns [2]testbench.TCPIPv4
|
||||
defer func() {
|
||||
for i := range connectingConns {
|
||||
connectingConns[i].Close(t)
|
||||
}
|
||||
}()
|
||||
{
|
||||
var wg sync.WaitGroup
|
||||
for i := range connectingConns {
|
||||
conn := &connectingConns[i]
|
||||
*conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{})
|
||||
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
dut.Shutdown(t, listenFd, unix.SHUT_RD)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Shutdown causes Connections in the accept queue to be closed.
|
||||
for i := range synQueueConns {
|
||||
conn := &synQueueConns[i]
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagRst|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := range connectingConns {
|
||||
conn := &connectingConns[i]
|
||||
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
// The pending connection in the SYN queue is now a zombie on gVisor.
|
||||
//
|
||||
// TODO(https://gvisor.dev/issues/6671): Stop retransmitting the SYN-ACK.
|
||||
if i == 0 && !dut.Uname.IsLinux() {
|
||||
seqNum := uint32(*conn.RemoteSeqNum(t) - 1)
|
||||
if got, err := conn.Expect(t, testbench.TCP{SeqNum: &seqNum}, time.Second); err != nil {
|
||||
t.Errorf("%d: expected TCP frame: %s", i, err)
|
||||
} else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want {
|
||||
t.Errorf("%d: got %s, want %s", i, got, want)
|
||||
}
|
||||
} else {
|
||||
if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil {
|
||||
t.Errorf("%d: expected no TCP frame, got %s", i, got)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user