gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/ipv6_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 ipv6_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"
    25  	"gvisor.dev/gvisor/pkg/tcpip/checksum"
    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  type fragmentInfo struct {
    35  	offset uint16
    36  	size   uint16
    37  	more   bool
    38  	id     uint32
    39  }
    40  
    41  func TestIPv6FragmentReassembly(t *testing.T) {
    42  	icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
    43  
    44  	tests := []struct {
    45  		description  string
    46  		ipPayloadLen int
    47  		fragments    []fragmentInfo
    48  		expectReply  bool
    49  	}{
    50  		{
    51  			description:  "basic reassembly",
    52  			ipPayloadLen: 3000,
    53  			fragments: []fragmentInfo{
    54  				{offset: 0, size: 1000, id: 100, more: true},
    55  				{offset: 1000, size: 1000, id: 100, more: true},
    56  				{offset: 2000, size: 1000, id: 100, more: false},
    57  			},
    58  			expectReply: true,
    59  		},
    60  		{
    61  			description:  "out of order fragments",
    62  			ipPayloadLen: 3000,
    63  			fragments: []fragmentInfo{
    64  				{offset: 0, size: 1000, id: 101, more: true},
    65  				{offset: 2000, size: 1000, id: 101, more: false},
    66  				{offset: 1000, size: 1000, id: 101, more: true},
    67  			},
    68  			expectReply: true,
    69  		},
    70  		{
    71  			description:  "duplicated fragments",
    72  			ipPayloadLen: 3000,
    73  			fragments: []fragmentInfo{
    74  				{offset: 0, size: 1000, id: 102, more: true},
    75  				{offset: 1000, size: 1000, id: 102, more: true},
    76  				{offset: 1000, size: 1000, id: 102, more: true},
    77  				{offset: 2000, size: 1000, id: 102, more: false},
    78  			},
    79  			expectReply: true,
    80  		},
    81  		{
    82  			description:  "fragment subset",
    83  			ipPayloadLen: 3000,
    84  			fragments: []fragmentInfo{
    85  				{offset: 0, size: 1000, id: 103, more: true},
    86  				{offset: 1000, size: 1000, id: 103, more: true},
    87  				{offset: 512, size: 256, id: 103, more: true},
    88  				{offset: 2000, size: 1000, id: 103, more: false},
    89  			},
    90  			expectReply: true,
    91  		},
    92  		{
    93  			description:  "fragment overlap",
    94  			ipPayloadLen: 3000,
    95  			fragments: []fragmentInfo{
    96  				{offset: 0, size: 1000, id: 104, more: true},
    97  				{offset: 1512, size: 1000, id: 104, more: true},
    98  				{offset: 1000, size: 1000, id: 104, more: true},
    99  				{offset: 2000, size: 1000, id: 104, more: false},
   100  			},
   101  			expectReply: false,
   102  		},
   103  	}
   104  
   105  	for _, test := range tests {
   106  		t.Run(test.description, func(t *testing.T) {
   107  			dut := testbench.NewDUT(t)
   108  			conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
   109  			defer conn.Close(t)
   110  
   111  			lIP := tcpip.AddrFrom16Slice(dut.Net.LocalIPv6)
   112  			rIP := tcpip.AddrFrom16Slice(dut.Net.RemoteIPv6)
   113  
   114  			data := make([]byte, test.ipPayloadLen)
   115  			icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize])
   116  			icmp.SetType(header.ICMPv6EchoRequest)
   117  			icmp.SetCode(header.ICMPv6UnusedCode)
   118  			icmp.SetChecksum(0)
   119  			originalPayload := data[header.ICMPv6HeaderSize:]
   120  			if _, err := rand.Read(originalPayload); err != nil {
   121  				t.Fatalf("rand.Read: %s", err)
   122  			}
   123  
   124  			cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
   125  				Header:      icmp,
   126  				Src:         lIP,
   127  				Dst:         rIP,
   128  				PayloadCsum: checksum.Checksum(originalPayload, 0 /* initial */),
   129  				PayloadLen:  len(originalPayload),
   130  			})
   131  			icmp.SetChecksum(cksum)
   132  
   133  			for _, fragment := range test.fragments {
   134  				conn.Send(t, testbench.IPv6{},
   135  					&testbench.IPv6FragmentExtHdr{
   136  						NextHeader:     &icmpv6ProtoNum,
   137  						FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit),
   138  						MoreFragments:  testbench.Bool(fragment.more),
   139  						Identification: testbench.Uint32(fragment.id),
   140  					},
   141  					&testbench.Payload{
   142  						Bytes: data[fragment.offset:][:fragment.size],
   143  					})
   144  			}
   145  
   146  			var bytesReceived int
   147  			reassembledPayload := make([]byte, test.ipPayloadLen)
   148  			for {
   149  				incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
   150  					&testbench.Ether{},
   151  					&testbench.IPv6{},
   152  					&testbench.IPv6FragmentExtHdr{},
   153  				}, time.Second)
   154  				if err != nil {
   155  					// Either an unexpected frame was received, or none at all.
   156  					if test.expectReply && bytesReceived < test.ipPayloadLen {
   157  						t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
   158  					}
   159  					break
   160  				}
   161  				if !test.expectReply {
   162  					t.Fatalf("unexpected reply received:\n%s", incomingFrame)
   163  				}
   164  				ipPayload, err := incomingFrame[3 /* Payload */].ToBytes()
   165  				if err != nil {
   166  					t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err)
   167  				}
   168  				offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset
   169  				offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit
   170  				if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
   171  					t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
   172  				}
   173  				bytesReceived += len(ipPayload)
   174  			}
   175  
   176  			if test.expectReply {
   177  				if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" {
   178  					t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
   179  				}
   180  			}
   181  		})
   182  	}
   183  }