gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/tcp_linger_test.go (about)

     1  // Copyright 2020 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_linger_test
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  	"testing"
    21  	"time"
    22  
    23  	"golang.org/x/sys/unix"
    24  	"gvisor.dev/gvisor/pkg/tcpip/header"
    25  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    26  )
    27  
    28  func init() {
    29  	testbench.Initialize(flag.CommandLine)
    30  }
    31  
    32  func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) {
    33  	listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
    34  	conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
    35  	conn.Connect(t)
    36  	acceptFD, _ := dut.Accept(t, listenFD)
    37  	return acceptFD, listenFD, conn
    38  }
    39  
    40  func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) {
    41  	conn.Close(t)
    42  	dut.Close(t, listenFD)
    43  }
    44  
    45  // lingerDuration is the timeout value used with SO_LINGER socket option.
    46  const lingerDuration = 3 * time.Second
    47  
    48  // TestTCPLingerZeroTimeout tests when SO_LINGER is set with zero timeout. DUT
    49  // should send RST-ACK when socket is closed.
    50  func TestTCPLingerZeroTimeout(t *testing.T) {
    51  	// Create a socket, listen, TCP connect, and accept.
    52  	dut := testbench.NewDUT(t)
    53  	acceptFD, listenFD, conn := createSocket(t, dut)
    54  	defer closeAll(t, dut, listenFD, conn)
    55  
    56  	dut.SetSockLingerOption(t, acceptFD, 0, true)
    57  	dut.Close(t, acceptFD)
    58  
    59  	// If the linger timeout is set to zero, the DUT should send a RST.
    60  	if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil {
    61  		t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
    62  	}
    63  	conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
    64  }
    65  
    66  // TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK
    67  // when socket is closed.
    68  func TestTCPLingerOff(t *testing.T) {
    69  	// Create a socket, listen, TCP connect, and accept.
    70  	dut := testbench.NewDUT(t)
    71  	acceptFD, listenFD, conn := createSocket(t, dut)
    72  	defer closeAll(t, dut, listenFD, conn)
    73  
    74  	dut.Close(t, acceptFD)
    75  
    76  	// If SO_LINGER is not set, DUT should send a FIN-ACK.
    77  	if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
    78  		t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
    79  	}
    80  	conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
    81  }
    82  
    83  // TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout.
    84  // DUT should close the socket after timeout.
    85  func TestTCPLingerNonZeroTimeout(t *testing.T) {
    86  	for _, tt := range []struct {
    87  		description string
    88  		lingerOn    bool
    89  	}{
    90  		{"WithNonZeroLinger", true},
    91  		{"WithoutLinger", false},
    92  	} {
    93  		t.Run(tt.description, func(t *testing.T) {
    94  			// Create a socket, listen, TCP connect, and accept.
    95  			dut := testbench.NewDUT(t)
    96  			acceptFD, listenFD, conn := createSocket(t, dut)
    97  			defer closeAll(t, dut, listenFD, conn)
    98  
    99  			dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
   100  
   101  			start := time.Now()
   102  			dut.CloseWithErrno(context.Background(), t, acceptFD)
   103  			elapsed := time.Since(start)
   104  
   105  			expectedMaximum := time.Second
   106  			if tt.lingerOn {
   107  				expectedMaximum += lingerDuration
   108  				if elapsed < lingerDuration {
   109  					t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
   110  				}
   111  			}
   112  			if elapsed >= expectedMaximum {
   113  				t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
   114  			}
   115  
   116  			if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
   117  				t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
   118  			}
   119  			conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
   120  		})
   121  	}
   122  }
   123  
   124  // TestTCPLingerSendNonZeroTimeout tests when SO_LINGER is set with non-zero
   125  // timeout and send a packet. DUT should close the socket after timeout.
   126  func TestTCPLingerSendNonZeroTimeout(t *testing.T) {
   127  	for _, tt := range []struct {
   128  		description string
   129  		lingerOn    bool
   130  	}{
   131  		{"WithSendNonZeroLinger", true},
   132  		{"WithoutLinger", false},
   133  	} {
   134  		t.Run(tt.description, func(t *testing.T) {
   135  			// Create a socket, listen, TCP connect, and accept.
   136  			dut := testbench.NewDUT(t)
   137  			acceptFD, listenFD, conn := createSocket(t, dut)
   138  			defer closeAll(t, dut, listenFD, conn)
   139  
   140  			dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
   141  
   142  			// Send data.
   143  			sampleData := []byte("Sample Data")
   144  			dut.Send(t, acceptFD, sampleData, 0)
   145  
   146  			start := time.Now()
   147  			dut.CloseWithErrno(context.Background(), t, acceptFD)
   148  			elapsed := time.Since(start)
   149  
   150  			expectedMaximum := time.Second
   151  			if tt.lingerOn {
   152  				expectedMaximum += lingerDuration
   153  				if elapsed < lingerDuration {
   154  					t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
   155  				}
   156  			}
   157  			if elapsed >= expectedMaximum {
   158  				t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
   159  			}
   160  
   161  			samplePayload := &testbench.Payload{Bytes: sampleData}
   162  			if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
   163  				t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
   164  			}
   165  
   166  			if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
   167  				t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
   168  			}
   169  			conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
   170  		})
   171  	}
   172  }
   173  
   174  // TestTCPLingerShutdownZeroTimeout tests SO_LINGER with shutdown() and zero
   175  // timeout. DUT should send RST-ACK when socket is closed.
   176  func TestTCPLingerShutdownZeroTimeout(t *testing.T) {
   177  	// Create a socket, listen, TCP connect, and accept.
   178  	dut := testbench.NewDUT(t)
   179  	acceptFD, listenFD, conn := createSocket(t, dut)
   180  	defer closeAll(t, dut, listenFD, conn)
   181  
   182  	dut.SetSockLingerOption(t, acceptFD, 0, true)
   183  	dut.Shutdown(t, acceptFD, unix.SHUT_RDWR)
   184  	dut.Close(t, acceptFD)
   185  
   186  	// Shutdown will send FIN-ACK with read/write option.
   187  	if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
   188  		t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
   189  	}
   190  
   191  	// If the linger timeout is set to zero, the DUT should send a RST.
   192  	if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil {
   193  		t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
   194  	}
   195  	conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
   196  }
   197  
   198  // TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and
   199  // non-zero timeout. DUT should close the socket after timeout.
   200  func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) {
   201  	for _, tt := range []struct {
   202  		description string
   203  		lingerOn    bool
   204  	}{
   205  		{"shutdownRDWR", true},
   206  		{"shutdownRDWR", false},
   207  	} {
   208  		t.Run(tt.description, func(t *testing.T) {
   209  			// Create a socket, listen, TCP connect, and accept.
   210  			dut := testbench.NewDUT(t)
   211  			acceptFD, listenFD, conn := createSocket(t, dut)
   212  			defer closeAll(t, dut, listenFD, conn)
   213  
   214  			dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
   215  
   216  			// Send data.
   217  			sampleData := []byte("Sample Data")
   218  			dut.Send(t, acceptFD, sampleData, 0)
   219  
   220  			dut.Shutdown(t, acceptFD, unix.SHUT_RDWR)
   221  
   222  			start := time.Now()
   223  			dut.CloseWithErrno(context.Background(), t, acceptFD)
   224  			elapsed := time.Since(start)
   225  
   226  			expectedMaximum := time.Second
   227  			if tt.lingerOn {
   228  				expectedMaximum += lingerDuration
   229  				if elapsed < lingerDuration {
   230  					t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
   231  				}
   232  			}
   233  			if elapsed >= expectedMaximum {
   234  				t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
   235  			}
   236  
   237  			samplePayload := &testbench.Payload{Bytes: sampleData}
   238  			if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
   239  				t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
   240  			}
   241  
   242  			if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
   243  				t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
   244  			}
   245  			conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
   246  		})
   247  	}
   248  }
   249  
   250  func TestTCPLingerNonEstablished(t *testing.T) {
   251  	dut := testbench.NewDUT(t)
   252  	newFD := dut.Socket(t, unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP)
   253  	dut.SetSockLingerOption(t, newFD, lingerDuration, true)
   254  
   255  	// As the socket is in the initial state, Close() should not linger
   256  	// and return immediately.
   257  	start := time.Now()
   258  	dut.CloseWithErrno(context.Background(), t, newFD)
   259  	elapsed := time.Since(start)
   260  
   261  	expectedMaximum := time.Second
   262  	if elapsed >= time.Second {
   263  		t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
   264  	}
   265  }