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