gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/transport/tcp/test/e2e/tcp_timestamp_test.go (about)

     1  // Copyright 2018 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_timestamp_test
    16  
    17  import (
    18  	"bytes"
    19  	"math/rand"
    20  	"os"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"gvisor.dev/gvisor/pkg/refs"
    26  	"gvisor.dev/gvisor/pkg/tcpip"
    27  	"gvisor.dev/gvisor/pkg/tcpip/checker"
    28  	"gvisor.dev/gvisor/pkg/tcpip/header"
    29  	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
    30  	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/test/e2e"
    31  	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
    32  	"gvisor.dev/gvisor/pkg/waiter"
    33  )
    34  
    35  // createConnectedWithTimestampOption creates and connects c.ep with the
    36  // timestamp option enabled.
    37  func createConnectedWithTimestampOption(c *context.Context) *context.RawEndpoint {
    38  	return c.CreateConnectedWithOptionsNoDelay(header.TCPSynOptions{TS: true, TSVal: 1})
    39  }
    40  
    41  // TestTimeStampEnabledConnect tests that netstack sends the timestamp option on
    42  // an active connect and sets the TS Echo Reply fields correctly when the
    43  // SYN-ACK also indicates support for the TS option and provides a TSVal.
    44  func TestTimeStampEnabledConnect(t *testing.T) {
    45  	c := context.New(t, e2e.DefaultMTU)
    46  	defer c.Cleanup()
    47  
    48  	rep := createConnectedWithTimestampOption(c)
    49  
    50  	// Register for read and validate that we have data to read.
    51  	we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
    52  	c.WQ.EventRegister(&we)
    53  	defer c.WQ.EventUnregister(&we)
    54  
    55  	// The following tests ensure that TS option once enabled behaves
    56  	// correctly as described in
    57  	// https://tools.ietf.org/html/rfc7323#section-4.3.
    58  	//
    59  	// We are not testing delayed ACKs here, but we do test out of order
    60  	// packet delivery and filling the sequence number hole created due to
    61  	// the out of order packet.
    62  	//
    63  	// The test also verifies that the sequence numbers and timestamps are
    64  	// as expected.
    65  	data := []byte{1, 2, 3}
    66  
    67  	// First we increment tsVal by a small amount.
    68  	tsVal := rep.TSVal + 100
    69  	rep.SendPacketWithTS(data, tsVal)
    70  	rep.VerifyACKWithTS(tsVal)
    71  
    72  	// Next we send an out of order packet.
    73  	rep.NextSeqNum += 3
    74  	tsVal += 200
    75  	rep.SendPacketWithTS(data, tsVal)
    76  
    77  	// The ACK should contain the original sequenceNumber and an older TS.
    78  	rep.NextSeqNum -= 6
    79  	rep.VerifyACKWithTS(tsVal - 200)
    80  
    81  	// Next we fill the hole and the returned ACK should contain the
    82  	// cumulative sequence number acking all data sent till now and have the
    83  	// latest timestamp sent below in its TSEcr field.
    84  	tsVal -= 100
    85  	rep.SendPacketWithTS(data, tsVal)
    86  	rep.NextSeqNum += 3
    87  	rep.VerifyACKWithTS(tsVal)
    88  
    89  	// Increment tsVal by a large value that doesn't result in a wrap around.
    90  	tsVal += 0x7fffffff
    91  	rep.SendPacketWithTS(data, tsVal)
    92  	rep.VerifyACKWithTS(tsVal)
    93  
    94  	// Increment tsVal again by a large value which should cause the
    95  	// timestamp value to wrap around. The returned ACK should contain the
    96  	// wrapped around timestamp in its tsEcr field and not the tsVal from
    97  	// the previous packet sent above.
    98  	tsVal += 0x7fffffff
    99  	rep.SendPacketWithTS(data, tsVal)
   100  	rep.VerifyACKWithTS(tsVal)
   101  
   102  	select {
   103  	case <-ch:
   104  	case <-time.After(1 * time.Second):
   105  		t.Fatalf("Timed out waiting for data to arrive")
   106  	}
   107  
   108  	// There should be 5 views to read and each of them should
   109  	// contain the same data.
   110  	for i := 0; i < 5; i++ {
   111  		buf := make([]byte, len(data))
   112  		w := tcpip.SliceWriter(buf)
   113  		result, err := c.EP.Read(&w, tcpip.ReadOptions{})
   114  		if err != nil {
   115  			t.Fatalf("Unexpected error from Read: %v", err)
   116  		}
   117  		if diff := cmp.Diff(tcpip.ReadResult{
   118  			Count: len(buf),
   119  			Total: len(buf),
   120  		}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
   121  			t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
   122  		}
   123  		if got, want := buf, data; bytes.Compare(got, want) != 0 {
   124  			t.Fatalf("Data is different: got: %v, want: %v", got, want)
   125  		}
   126  	}
   127  }
   128  
   129  // TestTimeStampDisabledConnect tests that netstack sends timestamp option on an
   130  // active connect but if the SYN-ACK doesn't specify the TS option then
   131  // timestamp option is not enabled and future packets do not contain a
   132  // timestamp.
   133  func TestTimeStampDisabledConnect(t *testing.T) {
   134  	c := context.New(t, e2e.DefaultMTU)
   135  	defer c.Cleanup()
   136  
   137  	c.CreateConnectedWithOptionsNoDelay(header.TCPSynOptions{})
   138  }
   139  
   140  func timeStampEnabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
   141  	c := context.New(t, e2e.DefaultMTU)
   142  	defer c.Cleanup()
   143  
   144  	if cookieEnabled {
   145  		opt := tcpip.TCPAlwaysUseSynCookies(true)
   146  		if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
   147  			t.Fatalf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err)
   148  		}
   149  	}
   150  
   151  	t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
   152  	tsVal := rand.Uint32()
   153  	c.AcceptWithOptionsNoDelay(wndScale, header.TCPSynOptions{MSS: e2e.DefaultIPv4MSS, TS: true, TSVal: tsVal})
   154  
   155  	// Now send some data and validate that timestamp is echoed correctly in the ACK.
   156  	data := []byte{1, 2, 3}
   157  
   158  	var r bytes.Reader
   159  	r.Reset(data)
   160  	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
   161  		t.Fatalf("Unexpected error from Write: %s", err)
   162  	}
   163  
   164  	// Check that data is received and that the timestamp option TSEcr field
   165  	// matches the expected value.
   166  	b := c.GetPacket()
   167  	defer b.Release()
   168  	checker.IPv4(t, b,
   169  		// Add 12 bytes for the timestamp option + 2 NOPs to align at 4
   170  		// byte boundary.
   171  		checker.PayloadLen(len(data)+header.TCPMinimumSize+12),
   172  		checker.TCP(
   173  			checker.DstPort(context.TestPort),
   174  			checker.TCPSeqNum(uint32(c.IRS)+1),
   175  			checker.TCPAckNum(790),
   176  			checker.TCPWindow(wndSize),
   177  			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
   178  			checker.TCPTimestampChecker(true, 0, tsVal+1),
   179  		),
   180  	)
   181  }
   182  
   183  // TestTimeStampEnabledAccept tests that if the SYN on a passive connect
   184  // specifies the Timestamp option then the Timestamp option is sent on a SYN-ACK
   185  // and echoes the tsVal field of the original SYN in the tcEcr field of the
   186  // SYN-ACK. We cover the cases where SYN cookies are enabled/disabled and verify
   187  // that Timestamp option is enabled in both cases if requested in the original
   188  // SYN.
   189  func TestTimeStampEnabledAccept(t *testing.T) {
   190  	testCases := []struct {
   191  		cookieEnabled bool
   192  		wndScale      int
   193  		wndSize       uint16
   194  	}{
   195  		{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
   196  		// DefaultReceiveBufferSize is 1MB >> 5. Advertised window will be 1/2 of that.
   197  		{false, 5, 0x4000},
   198  	}
   199  	for _, tc := range testCases {
   200  		timeStampEnabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
   201  	}
   202  }
   203  
   204  func timeStampDisabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
   205  	c := context.New(t, e2e.DefaultMTU)
   206  	defer c.Cleanup()
   207  
   208  	if cookieEnabled {
   209  		opt := tcpip.TCPAlwaysUseSynCookies(true)
   210  		if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
   211  			t.Fatalf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err)
   212  		}
   213  	}
   214  
   215  	t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
   216  	c.AcceptWithOptionsNoDelay(wndScale, header.TCPSynOptions{MSS: e2e.DefaultIPv4MSS})
   217  
   218  	// Now send some data with the accepted connection endpoint and validate
   219  	// that no timestamp option is sent in the TCP segment.
   220  	data := []byte{1, 2, 3}
   221  
   222  	var r bytes.Reader
   223  	r.Reset(data)
   224  	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
   225  		t.Fatalf("Unexpected error from Write: %s", err)
   226  	}
   227  
   228  	// Check that data is received and that the timestamp option is disabled
   229  	// when SYN cookies are enabled/disabled.
   230  	b := c.GetPacket()
   231  	defer b.Release()
   232  	checker.IPv4(t, b,
   233  		checker.PayloadLen(len(data)+header.TCPMinimumSize),
   234  		checker.TCP(
   235  			checker.DstPort(context.TestPort),
   236  			checker.TCPSeqNum(uint32(c.IRS)+1),
   237  			checker.TCPAckNum(790),
   238  			checker.TCPWindow(wndSize),
   239  			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
   240  			checker.TCPTimestampChecker(false, 0, 0),
   241  		),
   242  	)
   243  }
   244  
   245  // TestTimeStampDisabledAccept tests that Timestamp option is not used when the
   246  // peer doesn't advertise it and connection is established with Accept().
   247  func TestTimeStampDisabledAccept(t *testing.T) {
   248  	testCases := []struct {
   249  		cookieEnabled bool
   250  		wndScale      int
   251  		wndSize       uint16
   252  	}{
   253  		{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
   254  		// DefaultReceiveBufferSize is 1MB >> 5. Advertised window will be half of
   255  		// that.
   256  		{false, 5, 0x4000},
   257  	}
   258  	for _, tc := range testCases {
   259  		timeStampDisabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
   260  	}
   261  }
   262  
   263  func TestSendGreaterThanMTUWithOptions(t *testing.T) {
   264  	const maxPayload = 100
   265  	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
   266  	defer c.Cleanup()
   267  
   268  	createConnectedWithTimestampOption(c)
   269  	e2e.CheckBrokenUpWrite(t, c, maxPayload)
   270  }
   271  
   272  func TestSegmentNotDroppedWhenTimestampMissing(t *testing.T) {
   273  	const maxPayload = 100
   274  	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
   275  	defer c.Cleanup()
   276  
   277  	rep := createConnectedWithTimestampOption(c)
   278  
   279  	// Register for read.
   280  	we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
   281  	c.WQ.EventRegister(&we)
   282  	defer c.WQ.EventUnregister(&we)
   283  
   284  	droppedPacketsStat := c.Stack().Stats().DroppedPackets
   285  	droppedPackets := droppedPacketsStat.Value()
   286  	data := []byte{1, 2, 3}
   287  	// Send a packet with no TCP options/timestamp.
   288  	rep.SendPacket(data, nil)
   289  
   290  	select {
   291  	case <-ch:
   292  	case <-time.After(1 * time.Second):
   293  		t.Fatalf("Timed out waiting for data to arrive")
   294  	}
   295  
   296  	// Assert that DroppedPackets was not incremented.
   297  	if got, want := droppedPacketsStat.Value(), droppedPackets; got != want {
   298  		t.Fatalf("incorrect number of dropped packets, got: %v, want: %v", got, want)
   299  	}
   300  
   301  	// Issue a read and we should data.
   302  	var buf bytes.Buffer
   303  	result, err := c.EP.Read(&buf, tcpip.ReadOptions{})
   304  	if err != nil {
   305  		t.Fatalf("Unexpected error from Read: %v", err)
   306  	}
   307  	if diff := cmp.Diff(tcpip.ReadResult{
   308  		Count: buf.Len(),
   309  		Total: buf.Len(),
   310  	}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
   311  		t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
   312  	}
   313  	if got, want := buf.Bytes(), data; bytes.Compare(got, want) != 0 {
   314  		t.Fatalf("Data is different: got: %v, want: %v", got, want)
   315  	}
   316  }
   317  
   318  func TestMain(m *testing.M) {
   319  	refs.SetLeakMode(refs.LeaksPanic)
   320  	code := m.Run()
   321  	// Allow TCP async work to complete to avoid false reports of leaks.
   322  	// TODO(gvisor.dev/issue/5940): Use fake clock in tests.
   323  	time.Sleep(1 * time.Second)
   324  	refs.DoLeakCheck()
   325  	os.Exit(code)
   326  }