gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/ipv6_fragment_icmp_error_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_icmp_error_test
    16  
    17  import (
    18  	"flag"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"gvisor.dev/gvisor/pkg/tcpip"
    24  	"gvisor.dev/gvisor/pkg/tcpip/checksum"
    25  	"gvisor.dev/gvisor/pkg/tcpip/header"
    26  	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
    27  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    28  )
    29  
    30  const (
    31  	data              = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT"
    32  	fragmentID        = 1
    33  	reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second
    34  )
    35  
    36  func init() {
    37  	testbench.Initialize(flag.CommandLine)
    38  }
    39  
    40  func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.IPv6Conn, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) {
    41  	t.Helper()
    42  
    43  	icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize))
    44  	icmpv6Header.SetType(header.ICMPv6EchoRequest)
    45  	icmpv6Header.SetCode(header.ICMPv6UnusedCode)
    46  	icmpv6Header.SetIdent(0)
    47  	icmpv6Header.SetSequence(0)
    48  	cksum := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
    49  		Header:      icmpv6Header,
    50  		Src:         tcpip.AddrFrom16Slice(n.LocalIPv6),
    51  		Dst:         tcpip.AddrFrom16Slice(n.RemoteIPv6),
    52  		PayloadCsum: checksum.Checksum(payload, 0 /* initial */),
    53  		PayloadLen:  len(payload),
    54  	})
    55  	icmpv6Header.SetChecksum(cksum)
    56  	icmpv6Bytes := append([]byte(icmpv6Header), payload...)
    57  
    58  	icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
    59  
    60  	firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
    61  		&testbench.IPv6FragmentExtHdr{
    62  			NextHeader:     &icmpv6ProtoNum,
    63  			FragmentOffset: testbench.Uint16(0),
    64  			MoreFragments:  testbench.Bool(true),
    65  			Identification: testbench.Uint32(fragmentID),
    66  		},
    67  		&testbench.Payload{
    68  			Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength],
    69  		},
    70  	)
    71  	firstIPv6 := firstFragment[1:]
    72  	firstIPv6Bytes, err := firstIPv6.ToBytes()
    73  	if err != nil {
    74  		t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err)
    75  	}
    76  
    77  	secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
    78  		&testbench.IPv6FragmentExtHdr{
    79  			NextHeader:     &icmpv6ProtoNum,
    80  			FragmentOffset: testbench.Uint16(secondFragmentOffset),
    81  			MoreFragments:  testbench.Bool(false),
    82  			Identification: testbench.Uint32(fragmentID),
    83  		},
    84  		&testbench.Payload{
    85  			Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:],
    86  		},
    87  	)
    88  	secondIPv6 := secondFragment[1:]
    89  	secondIPv6Bytes, err := secondIPv6.ToBytes()
    90  	if err != nil {
    91  		t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err)
    92  	}
    93  
    94  	return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes}
    95  }
    96  
    97  func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) {
    98  	tests := []struct {
    99  		name                 string
   100  		firstPayloadLength   uint16
   101  		payload              []byte
   102  		secondFragmentOffset uint16
   103  		sendFrameOrder       []int
   104  	}{
   105  		{
   106  			name:                 "reassemble two fragments",
   107  			firstPayloadLength:   8,
   108  			payload:              []byte(data)[:20],
   109  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
   110  			sendFrameOrder:       []int{1, 2},
   111  		},
   112  		{
   113  			name:                 "reassemble two fragments in reverse order",
   114  			firstPayloadLength:   8,
   115  			payload:              []byte(data)[:20],
   116  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
   117  			sendFrameOrder:       []int{2, 1},
   118  		},
   119  	}
   120  
   121  	for _, test := range tests {
   122  		t.Run(test.name, func(t *testing.T) {
   123  			t.Parallel()
   124  			dut := testbench.NewDUT(t)
   125  			conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
   126  			defer conn.Close(t)
   127  
   128  			fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
   129  
   130  			for _, i := range test.sendFrameOrder {
   131  				conn.SendFrame(t, fragments[i-1])
   132  			}
   133  
   134  			gotEchoReply, err := conn.ExpectFrame(t, testbench.Layers{
   135  				&testbench.Ether{},
   136  				&testbench.IPv6{},
   137  				&testbench.ICMPv6{
   138  					Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
   139  					Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
   140  				},
   141  			}, time.Second)
   142  			if err != nil {
   143  				t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err)
   144  			}
   145  			gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes()
   146  			if err != nil {
   147  				t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
   148  			}
   149  			icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:]
   150  			wantPayload := test.payload
   151  			if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
   152  				t.Fatalf("payload mismatch (-want +got):\n%s", diff)
   153  			}
   154  		})
   155  	}
   156  }
   157  
   158  func TestIPv6FragmentReassemblyTimeout(t *testing.T) {
   159  	type icmpFramePattern struct {
   160  		typ  header.ICMPv6Type
   161  		code header.ICMPv6Code
   162  	}
   163  
   164  	type icmpReassemblyTimeoutDetail struct {
   165  		payloadFragment int // 1: first fragment, 2: second fragnemt.
   166  	}
   167  
   168  	tests := []struct {
   169  		name                        string
   170  		firstPayloadLength          uint16
   171  		payload                     []byte
   172  		secondFragmentOffset        uint16
   173  		sendFrameOrder              []int
   174  		replyFilter                 icmpFramePattern
   175  		expectErrorReply            bool
   176  		expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail
   177  	}{
   178  		{
   179  			name:                 "reassembly timeout (first fragment only)",
   180  			firstPayloadLength:   8,
   181  			payload:              []byte(data)[:20],
   182  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
   183  			sendFrameOrder:       []int{1},
   184  			replyFilter: icmpFramePattern{
   185  				typ:  header.ICMPv6TimeExceeded,
   186  				code: header.ICMPv6ReassemblyTimeout,
   187  			},
   188  			expectErrorReply: true,
   189  			expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
   190  				payloadFragment: 1,
   191  			},
   192  		},
   193  		{
   194  			name:                 "reassembly timeout (second fragment only)",
   195  			firstPayloadLength:   8,
   196  			payload:              []byte(data)[:20],
   197  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
   198  			sendFrameOrder:       []int{2},
   199  			replyFilter: icmpFramePattern{
   200  				typ:  header.ICMPv6TimeExceeded,
   201  				code: header.ICMPv6ReassemblyTimeout,
   202  			},
   203  			expectErrorReply: false,
   204  		},
   205  		{
   206  			name:                 "reassembly timeout (two fragments with a gap)",
   207  			firstPayloadLength:   8,
   208  			payload:              []byte(data)[:20],
   209  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8,
   210  			sendFrameOrder:       []int{1, 2},
   211  			replyFilter: icmpFramePattern{
   212  				typ:  header.ICMPv6TimeExceeded,
   213  				code: header.ICMPv6ReassemblyTimeout,
   214  			},
   215  			expectErrorReply: true,
   216  			expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
   217  				payloadFragment: 1,
   218  			},
   219  		},
   220  	}
   221  
   222  	for _, test := range tests {
   223  		t.Run(test.name, func(t *testing.T) {
   224  			t.Parallel()
   225  			dut := testbench.NewDUT(t)
   226  			conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
   227  			defer conn.Close(t)
   228  
   229  			fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
   230  
   231  			for _, i := range test.sendFrameOrder {
   232  				conn.SendFrame(t, fragments[i-1])
   233  			}
   234  
   235  			gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{
   236  				&testbench.Ether{},
   237  				&testbench.IPv6{},
   238  				&testbench.ICMPv6{
   239  					Type: testbench.ICMPv6Type(test.replyFilter.typ),
   240  					Code: testbench.ICMPv6Code(test.replyFilter.code),
   241  				},
   242  			}, reassemblyTimeout)
   243  			if !test.expectErrorReply {
   244  				if err == nil {
   245  					t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code)
   246  				}
   247  				return
   248  			}
   249  			if err != nil {
   250  				t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
   251  			}
   252  			gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
   253  			if err != nil {
   254  				t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
   255  			}
   256  			icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
   257  			wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1]
   258  			if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
   259  				t.Fatalf("payload mismatch (-want +got):\n%s", diff)
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func TestIPv6FragmentParamProblem(t *testing.T) {
   266  	type icmpFramePattern struct {
   267  		typ  header.ICMPv6Type
   268  		code header.ICMPv6Code
   269  	}
   270  
   271  	type icmpParamProblemDetail struct {
   272  		pointer         uint32
   273  		payloadFragment int // 1: first fragment, 2: second fragnemt.
   274  	}
   275  
   276  	tests := []struct {
   277  		name                   string
   278  		firstPayloadLength     uint16
   279  		payload                []byte
   280  		secondFragmentOffset   uint16
   281  		sendFrameOrder         []int
   282  		replyFilter            icmpFramePattern
   283  		expectICMPParamProblem icmpParamProblemDetail
   284  	}{
   285  		{
   286  			name:                 "payload size not a multiple of 8",
   287  			firstPayloadLength:   9,
   288  			payload:              []byte(data)[:20],
   289  			secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
   290  			sendFrameOrder:       []int{1},
   291  			replyFilter: icmpFramePattern{
   292  				typ:  header.ICMPv6ParamProblem,
   293  				code: header.ICMPv6ErroneousHeader,
   294  			},
   295  			expectICMPParamProblem: icmpParamProblemDetail{
   296  				pointer:         4,
   297  				payloadFragment: 1,
   298  			},
   299  		},
   300  		{
   301  			name:                 "payload length error",
   302  			firstPayloadLength:   16,
   303  			payload:              []byte(data)[:33],
   304  			secondFragmentOffset: 65520 / 8,
   305  			sendFrameOrder:       []int{1, 2},
   306  			replyFilter: icmpFramePattern{
   307  				typ:  header.ICMPv6ParamProblem,
   308  				code: header.ICMPv6ErroneousHeader,
   309  			},
   310  			expectICMPParamProblem: icmpParamProblemDetail{
   311  				pointer:         42,
   312  				payloadFragment: 2,
   313  			},
   314  		},
   315  	}
   316  
   317  	for _, test := range tests {
   318  		t.Run(test.name, func(t *testing.T) {
   319  			t.Parallel()
   320  			dut := testbench.NewDUT(t)
   321  			conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
   322  			defer conn.Close(t)
   323  
   324  			fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, &conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
   325  
   326  			for _, i := range test.sendFrameOrder {
   327  				conn.SendFrame(t, fragments[i-1])
   328  			}
   329  
   330  			gotErrorMessage, err := conn.ExpectFrame(t, testbench.Layers{
   331  				&testbench.Ether{},
   332  				&testbench.IPv6{},
   333  				&testbench.ICMPv6{
   334  					Type: testbench.ICMPv6Type(test.replyFilter.typ),
   335  					Code: testbench.ICMPv6Code(test.replyFilter.code),
   336  				},
   337  			}, time.Second)
   338  			if err != nil {
   339  				t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
   340  			}
   341  			gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
   342  			if err != nil {
   343  				t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
   344  			}
   345  			gotPointer := header.ICMPv6(gotPayload).TypeSpecific()
   346  			wantPointer := test.expectICMPParamProblem.pointer
   347  			if gotPointer != wantPointer {
   348  				t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer)
   349  			}
   350  			icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
   351  			wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1]
   352  			if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
   353  				t.Fatalf("payload mismatch (-want +got):\n%s", diff)
   354  			}
   355  		})
   356  	}
   357  }