gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/ipv4_id_uniqueness_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 ipv4_id_uniqueness_test
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"golang.org/x/sys/unix"
    25  	"gvisor.dev/gvisor/pkg/abi/linux"
    26  	"gvisor.dev/gvisor/pkg/tcpip/header"
    27  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    28  )
    29  
    30  func init() {
    31  	testbench.Initialize(flag.CommandLine)
    32  }
    33  
    34  func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) {
    35  	layers, err := conn.ExpectData(t, expect, expectPayload, time.Second)
    36  	if err != nil {
    37  		return 0, fmt.Errorf("failed to receive TCP segment: %s", err)
    38  	}
    39  	if len(layers) < 2 {
    40  		return 0, fmt.Errorf("got packet with layers: %v, expected to have at least 2 layers (link and network)", layers)
    41  	}
    42  	ipv4, ok := layers[1].(*testbench.IPv4)
    43  	if !ok {
    44  		return 0, fmt.Errorf("got network layer: %T, expected: *IPv4", layers[1])
    45  	}
    46  	if *ipv4.Flags&header.IPv4FlagDontFragment != 0 {
    47  		return 0, fmt.Errorf("got IPv4 DF=1, expected DF=0")
    48  	}
    49  	return *ipv4.ID, nil
    50  }
    51  
    52  // RFC 6864 section 4.2 states: "The IPv4 ID of non-atomic datagrams MUST NOT
    53  // be reused when sending a copy of an earlier non-atomic datagram."
    54  //
    55  // This test creates a TCP connection, uses the IP_MTU_DISCOVER socket option
    56  // to force the DF bit to be 0, and checks that a retransmitted segment has a
    57  // different IPv4 Identification value than the original segment.
    58  func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) {
    59  	for _, tc := range []struct {
    60  		name    string
    61  		payload []byte
    62  	}{
    63  		{"SmallPayload", []byte("sample data")},
    64  		// 512 bytes is chosen because sending more than this in a single segment
    65  		// causes the retransmission to send less than the original amount.
    66  		{"512BytePayload", testbench.GenerateRandomPayload(t, 512)},
    67  	} {
    68  		t.Run(tc.name, func(t *testing.T) {
    69  			dut := testbench.NewDUT(t)
    70  			listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
    71  			defer dut.Close(t, listenFD)
    72  
    73  			conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
    74  			defer conn.Close(t)
    75  
    76  			conn.Connect(t)
    77  			remoteFD, _ := dut.Accept(t, listenFD)
    78  			defer dut.Close(t, remoteFD)
    79  
    80  			dut.SetSockOptInt(t, remoteFD, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
    81  
    82  			// TODO(b/129291778) The following socket option clears the DF bit on
    83  			// IP packets sent over the socket, and is currently not supported by
    84  			// gVisor. gVisor by default sends packets with DF=0 anyway, so the
    85  			// socket option being not supported does not affect the operation of
    86  			// this test. Once the socket option is supported, the following call
    87  			// can be changed to simply assert success.
    88  			ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT)
    89  			// Fuchsia will return ENOPROTOPT errno.
    90  			if ret == -1 && errno != unix.ENOPROTOOPT {
    91  				t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno)
    92  			}
    93  
    94  			samplePayload := &testbench.Payload{Bytes: tc.payload}
    95  
    96  			dut.Send(t, remoteFD, tc.payload, 0)
    97  			if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
    98  				t.Fatalf("failed to receive TCP segment sent for RTT calculation: %s", err)
    99  			}
   100  			// Let the DUT estimate RTO with RTT from the DATA-ACK.
   101  			// TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
   102  			// we can skip sending this ACK.
   103  			conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
   104  
   105  			dut.Send(t, remoteFD, tc.payload, 0)
   106  			expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))}
   107  			originalID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
   108  			if err != nil {
   109  				t.Fatalf("failed to receive TCP segment: %s", err)
   110  			}
   111  
   112  			retransmitID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
   113  			if err != nil {
   114  				t.Fatalf("failed to receive retransmitted TCP segment: %s", err)
   115  			}
   116  			if originalID == retransmitID {
   117  				t.Fatalf("unexpectedly got retransmitted TCP segment with same IPv4 ID field=%d", originalID)
   118  			}
   119  		})
   120  	}
   121  }