gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/ipv4_fragment_reassembly_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_fragment_reassembly_test
    16  
    17  import (
    18  	"flag"
    19  	"math/rand"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"gvisor.dev/gvisor/pkg/tcpip/checksum"
    25  	"gvisor.dev/gvisor/pkg/tcpip/header"
    26  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    27  )
    28  
    29  func init() {
    30  	testbench.Initialize(flag.CommandLine)
    31  }
    32  
    33  type fragmentInfo struct {
    34  	offset uint16
    35  	size   uint16
    36  	more   uint8
    37  	id     uint16
    38  }
    39  
    40  func TestIPv4FragmentReassembly(t *testing.T) {
    41  	icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber)
    42  
    43  	tests := []struct {
    44  		description  string
    45  		ipPayloadLen int
    46  		fragments    []fragmentInfo
    47  		expectReply  bool
    48  	}{
    49  		{
    50  			description:  "basic reassembly",
    51  			ipPayloadLen: 3000,
    52  			fragments: []fragmentInfo{
    53  				{offset: 0, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
    54  				{offset: 1000, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
    55  				{offset: 2000, size: 1000, id: 5, more: 0},
    56  			},
    57  			expectReply: true,
    58  		},
    59  		{
    60  			description:  "out of order fragments",
    61  			ipPayloadLen: 3000,
    62  			fragments: []fragmentInfo{
    63  				{offset: 2000, size: 1000, id: 6, more: 0},
    64  				{offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
    65  				{offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
    66  			},
    67  			expectReply: true,
    68  		},
    69  		{
    70  			description:  "duplicated fragments",
    71  			ipPayloadLen: 3000,
    72  			fragments: []fragmentInfo{
    73  				{offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
    74  				{offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
    75  				{offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
    76  				{offset: 2000, size: 1000, id: 7, more: 0},
    77  			},
    78  			expectReply: true,
    79  		},
    80  		{
    81  			description:  "fragment subset",
    82  			ipPayloadLen: 3000,
    83  			fragments: []fragmentInfo{
    84  				{offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
    85  				{offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
    86  				{offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments},
    87  				{offset: 2000, size: 1000, id: 8, more: 0},
    88  			},
    89  			expectReply: true,
    90  		},
    91  		{
    92  			description:  "fragment overlap",
    93  			ipPayloadLen: 3000,
    94  			fragments: []fragmentInfo{
    95  				{offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
    96  				{offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
    97  				{offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
    98  				{offset: 2000, size: 1000, id: 9, more: 0},
    99  			},
   100  			expectReply: false,
   101  		},
   102  	}
   103  
   104  	for _, test := range tests {
   105  		t.Run(test.description, func(t *testing.T) {
   106  			dut := testbench.NewDUT(t)
   107  			conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
   108  			defer conn.Close(t)
   109  
   110  			data := make([]byte, test.ipPayloadLen)
   111  			icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize])
   112  			icmp.SetType(header.ICMPv4Echo)
   113  			icmp.SetCode(header.ICMPv4UnusedCode)
   114  			icmp.SetChecksum(0)
   115  			icmp.SetSequence(0)
   116  			icmp.SetIdent(0)
   117  			originalPayload := data[header.ICMPv4MinimumSize:]
   118  			if _, err := rand.Read(originalPayload); err != nil {
   119  				t.Fatalf("rand.Read: %s", err)
   120  			}
   121  			cksum := header.ICMPv4Checksum(icmp, checksum.Checksum(originalPayload, 0 /* initial */))
   122  			icmp.SetChecksum(cksum)
   123  
   124  			for _, fragment := range test.fragments {
   125  				conn.Send(t,
   126  					testbench.IPv4{
   127  						Protocol:       &icmpv4ProtoNum,
   128  						FragmentOffset: testbench.Uint16(fragment.offset),
   129  						Flags:          testbench.Uint8(fragment.more),
   130  						ID:             testbench.Uint16(fragment.id),
   131  					},
   132  					&testbench.Payload{
   133  						Bytes: data[fragment.offset:][:fragment.size],
   134  					})
   135  			}
   136  
   137  			var bytesReceived int
   138  			reassembledPayload := make([]byte, test.ipPayloadLen)
   139  			// We are sending a packet fragmented into smaller parts but the
   140  			// response may also be large enough to require fragmentation.
   141  			// Therefore we only look for payload for an IPv4 packet not ICMP.
   142  			for {
   143  				incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
   144  					&testbench.Ether{},
   145  					&testbench.IPv4{},
   146  				}, time.Second)
   147  				if err != nil {
   148  					// Either an unexpected frame was received, or none at all.
   149  					if test.expectReply && bytesReceived < test.ipPayloadLen {
   150  						t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
   151  					}
   152  					break
   153  				}
   154  				if !test.expectReply {
   155  					t.Fatalf("unexpected reply received:\n%s", incomingFrame)
   156  				}
   157  				// We only asked for Ethernet and IPv4 so the rest should be payload.
   158  				ipPayload, err := incomingFrame[2 /* Payload */].ToBytes()
   159  				if err != nil {
   160  					t.Fatalf("failed to parse payload: incomingPacket[2].ToBytes() = (_, %s)", err)
   161  				}
   162  				offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset
   163  				if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
   164  					t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
   165  				}
   166  				bytesReceived += len(ipPayload)
   167  			}
   168  
   169  			if test.expectReply {
   170  				if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" {
   171  					t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
   172  				}
   173  			}
   174  		})
   175  	}
   176  }