github.com/lightlus/netstack@v1.2.0/tcpip/network/ipv4/ipv4_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 ipv4_test
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/hex"
    20  	"math/rand"
    21  	"testing"
    22  
    23  	"github.com/lightlus/netstack/tcpip"
    24  	"github.com/lightlus/netstack/tcpip/buffer"
    25  	"github.com/lightlus/netstack/tcpip/header"
    26  	"github.com/lightlus/netstack/tcpip/link/channel"
    27  	"github.com/lightlus/netstack/tcpip/link/sniffer"
    28  	"github.com/lightlus/netstack/tcpip/network/ipv4"
    29  	"github.com/lightlus/netstack/tcpip/stack"
    30  	"github.com/lightlus/netstack/tcpip/transport/tcp"
    31  	"github.com/lightlus/netstack/tcpip/transport/udp"
    32  	"github.com/lightlus/netstack/waiter"
    33  )
    34  
    35  func TestExcludeBroadcast(t *testing.T) {
    36  	s := stack.New(stack.Options{
    37  		NetworkProtocols:   []stack.NetworkProtocol{ipv4.NewProtocol()},
    38  		TransportProtocols: []stack.TransportProtocol{udp.NewProtocol()},
    39  	})
    40  
    41  	const defaultMTU = 65536
    42  	ep := stack.LinkEndpoint(channel.New(256, defaultMTU, ""))
    43  	if testing.Verbose() {
    44  		ep = sniffer.New(ep)
    45  	}
    46  	if err := s.CreateNIC(1, ep); err != nil {
    47  		t.Fatalf("CreateNIC failed: %v", err)
    48  	}
    49  
    50  	s.SetRouteTable([]tcpip.Route{{
    51  		Destination: header.IPv4EmptySubnet,
    52  		NIC:         1,
    53  	}})
    54  
    55  	randomAddr := tcpip.FullAddress{NIC: 1, Addr: "\x0a\x00\x00\x01", Port: 53}
    56  
    57  	var wq waiter.Queue
    58  	t.Run("WithoutPrimaryAddress", func(t *testing.T) {
    59  		ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
    60  		if err != nil {
    61  			t.Fatal(err)
    62  		}
    63  		defer ep.Close()
    64  
    65  		// Cannot connect using a broadcast address as the source.
    66  		if err := ep.Connect(randomAddr); err != tcpip.ErrNoRoute {
    67  			t.Errorf("got ep.Connect(...) = %v, want = %v", err, tcpip.ErrNoRoute)
    68  		}
    69  
    70  		// However, we can bind to a broadcast address to listen.
    71  		if err := ep.Bind(tcpip.FullAddress{Addr: header.IPv4Broadcast, Port: 53, NIC: 1}); err != nil {
    72  			t.Errorf("Bind failed: %v", err)
    73  		}
    74  	})
    75  
    76  	t.Run("WithPrimaryAddress", func(t *testing.T) {
    77  		ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
    78  		if err != nil {
    79  			t.Fatal(err)
    80  		}
    81  		defer ep.Close()
    82  
    83  		// Add a valid primary endpoint address, now we can connect.
    84  		if err := s.AddAddress(1, ipv4.ProtocolNumber, "\x0a\x00\x00\x02"); err != nil {
    85  			t.Fatalf("AddAddress failed: %v", err)
    86  		}
    87  		if err := ep.Connect(randomAddr); err != nil {
    88  			t.Errorf("Connect failed: %v", err)
    89  		}
    90  	})
    91  }
    92  
    93  // makeHdrAndPayload generates a randomize packet. hdrLength indicates how much
    94  // data should already be in the header before WritePacket. extraLength
    95  // indicates how much extra space should be in the header. The payload is made
    96  // from many Views of the sizes listed in viewSizes.
    97  func makeHdrAndPayload(hdrLength int, extraLength int, viewSizes []int) (buffer.Prependable, buffer.VectorisedView) {
    98  	hdr := buffer.NewPrependable(hdrLength + extraLength)
    99  	hdr.Prepend(hdrLength)
   100  	rand.Read(hdr.View())
   101  
   102  	var views []buffer.View
   103  	totalLength := 0
   104  	for _, s := range viewSizes {
   105  		newView := buffer.NewView(s)
   106  		rand.Read(newView)
   107  		views = append(views, newView)
   108  		totalLength += s
   109  	}
   110  	payload := buffer.NewVectorisedView(totalLength, views)
   111  	return hdr, payload
   112  }
   113  
   114  // comparePayloads compared the contents of all the packets against the contents
   115  // of the source packet.
   116  func compareFragments(t *testing.T, packets []tcpip.PacketBuffer, sourcePacketInfo tcpip.PacketBuffer, mtu uint32) {
   117  	t.Helper()
   118  	// Make a complete array of the sourcePacketInfo packet.
   119  	source := header.IPv4(packets[0].Header.View()[:header.IPv4MinimumSize])
   120  	source = append(source, sourcePacketInfo.Header.View()...)
   121  	source = append(source, sourcePacketInfo.Data.ToView()...)
   122  
   123  	// Make a copy of the IP header, which will be modified in some fields to make
   124  	// an expected header.
   125  	sourceCopy := header.IPv4(append(buffer.View(nil), source[:source.HeaderLength()]...))
   126  	sourceCopy.SetChecksum(0)
   127  	sourceCopy.SetFlagsFragmentOffset(0, 0)
   128  	sourceCopy.SetTotalLength(0)
   129  	var offset uint16
   130  	// Build up an array of the bytes sent.
   131  	var reassembledPayload []byte
   132  	for i, packet := range packets {
   133  		// Confirm that the packet is valid.
   134  		allBytes := packet.Header.View().ToVectorisedView()
   135  		allBytes.Append(packet.Data)
   136  		ip := header.IPv4(allBytes.ToView())
   137  		if !ip.IsValid(len(ip)) {
   138  			t.Errorf("IP packet is invalid:\n%s", hex.Dump(ip))
   139  		}
   140  		if got, want := ip.CalculateChecksum(), uint16(0xffff); got != want {
   141  			t.Errorf("ip.CalculateChecksum() got %#x, want %#x", got, want)
   142  		}
   143  		if got, want := len(ip), int(mtu); got > want {
   144  			t.Errorf("fragment is too large, got %d want %d", got, want)
   145  		}
   146  		if got, want := packet.Header.UsedLength(), sourcePacketInfo.Header.UsedLength()+header.IPv4MinimumSize; i == 0 && want < int(mtu) && got != want {
   147  			t.Errorf("first fragment hdr parts should have unmodified length if possible: got %d, want %d", got, want)
   148  		}
   149  		if got, want := packet.Header.AvailableLength(), sourcePacketInfo.Header.AvailableLength()-header.IPv4MinimumSize; got != want {
   150  			t.Errorf("fragment #%d should have the same available space for prepending as source: got %d, want %d", i, got, want)
   151  		}
   152  		if i < len(packets)-1 {
   153  			sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()|header.IPv4FlagMoreFragments, offset)
   154  		} else {
   155  			sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()&^header.IPv4FlagMoreFragments, offset)
   156  		}
   157  		reassembledPayload = append(reassembledPayload, ip.Payload()...)
   158  		offset += ip.TotalLength() - uint16(ip.HeaderLength())
   159  		// Clear out the checksum and length from the ip because we can't compare
   160  		// it.
   161  		sourceCopy.SetTotalLength(uint16(len(ip)))
   162  		sourceCopy.SetChecksum(0)
   163  		sourceCopy.SetChecksum(^sourceCopy.CalculateChecksum())
   164  		if !bytes.Equal(ip[:ip.HeaderLength()], sourceCopy[:sourceCopy.HeaderLength()]) {
   165  			t.Errorf("ip[:ip.HeaderLength()] got:\n%s\nwant:\n%s", hex.Dump(ip[:ip.HeaderLength()]), hex.Dump(sourceCopy[:sourceCopy.HeaderLength()]))
   166  		}
   167  	}
   168  	expected := source[source.HeaderLength():]
   169  	if !bytes.Equal(reassembledPayload, expected) {
   170  		t.Errorf("reassembledPayload got:\n%s\nwant:\n%s", hex.Dump(reassembledPayload), hex.Dump(expected))
   171  	}
   172  }
   173  
   174  type errorChannel struct {
   175  	*channel.Endpoint
   176  	Ch                    chan tcpip.PacketBuffer
   177  	packetCollectorErrors []*tcpip.Error
   178  }
   179  
   180  // newErrorChannel creates a new errorChannel endpoint. Each call to WritePacket
   181  // will return successive errors from packetCollectorErrors until the list is
   182  // empty and then return nil each time.
   183  func newErrorChannel(size int, mtu uint32, linkAddr tcpip.LinkAddress, packetCollectorErrors []*tcpip.Error) *errorChannel {
   184  	return &errorChannel{
   185  		Endpoint:              channel.New(size, mtu, linkAddr),
   186  		Ch:                    make(chan tcpip.PacketBuffer, size),
   187  		packetCollectorErrors: packetCollectorErrors,
   188  	}
   189  }
   190  
   191  // Drain removes all outbound packets from the channel and counts them.
   192  func (e *errorChannel) Drain() int {
   193  	c := 0
   194  	for {
   195  		select {
   196  		case <-e.Ch:
   197  			c++
   198  		default:
   199  			return c
   200  		}
   201  	}
   202  }
   203  
   204  // WritePacket stores outbound packets into the channel.
   205  func (e *errorChannel) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt tcpip.PacketBuffer) *tcpip.Error {
   206  	select {
   207  	case e.Ch <- pkt:
   208  	default:
   209  	}
   210  
   211  	nextError := (*tcpip.Error)(nil)
   212  	if len(e.packetCollectorErrors) > 0 {
   213  		nextError = e.packetCollectorErrors[0]
   214  		e.packetCollectorErrors = e.packetCollectorErrors[1:]
   215  	}
   216  	return nextError
   217  }
   218  
   219  type context struct {
   220  	stack.Route
   221  	linkEP *errorChannel
   222  }
   223  
   224  func buildContext(t *testing.T, packetCollectorErrors []*tcpip.Error, mtu uint32) context {
   225  	// Make the packet and write it.
   226  	s := stack.New(stack.Options{
   227  		NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol()},
   228  	})
   229  	ep := newErrorChannel(100 /* Enough for all tests. */, mtu, "", packetCollectorErrors)
   230  	s.CreateNIC(1, ep)
   231  	const (
   232  		src = "\x10\x00\x00\x01"
   233  		dst = "\x10\x00\x00\x02"
   234  	)
   235  	s.AddAddress(1, ipv4.ProtocolNumber, src)
   236  	{
   237  		subnet, err := tcpip.NewSubnet(dst, tcpip.AddressMask(header.IPv4Broadcast))
   238  		if err != nil {
   239  			t.Fatal(err)
   240  		}
   241  		s.SetRouteTable([]tcpip.Route{{
   242  			Destination: subnet,
   243  			NIC:         1,
   244  		}})
   245  	}
   246  	r, err := s.FindRoute(0, src, dst, ipv4.ProtocolNumber, false /* multicastLoop */)
   247  	if err != nil {
   248  		t.Fatalf("s.FindRoute got %v, want %v", err, nil)
   249  	}
   250  	return context{
   251  		Route:  r,
   252  		linkEP: ep,
   253  	}
   254  }
   255  
   256  func TestFragmentation(t *testing.T) {
   257  	var manyPayloadViewsSizes [1000]int
   258  	for i := range manyPayloadViewsSizes {
   259  		manyPayloadViewsSizes[i] = 7
   260  	}
   261  	fragTests := []struct {
   262  		description       string
   263  		mtu               uint32
   264  		gso               *stack.GSO
   265  		hdrLength         int
   266  		extraLength       int
   267  		payloadViewsSizes []int
   268  		expectedFrags     int
   269  	}{
   270  		{"NoFragmentation", 2000, &stack.GSO{}, 0, header.IPv4MinimumSize, []int{1000}, 1},
   271  		{"NoFragmentationWithBigHeader", 2000, &stack.GSO{}, 16, header.IPv4MinimumSize, []int{1000}, 1},
   272  		{"Fragmented", 800, &stack.GSO{}, 0, header.IPv4MinimumSize, []int{1000}, 2},
   273  		{"FragmentedWithGsoNil", 800, nil, 0, header.IPv4MinimumSize, []int{1000}, 2},
   274  		{"FragmentedWithManyViews", 300, &stack.GSO{}, 0, header.IPv4MinimumSize, manyPayloadViewsSizes[:], 25},
   275  		{"FragmentedWithManyViewsAndPrependableBytes", 300, &stack.GSO{}, 0, header.IPv4MinimumSize + 55, manyPayloadViewsSizes[:], 25},
   276  		{"FragmentedWithBigHeader", 800, &stack.GSO{}, 20, header.IPv4MinimumSize, []int{1000}, 2},
   277  		{"FragmentedWithBigHeaderAndPrependableBytes", 800, &stack.GSO{}, 20, header.IPv4MinimumSize + 66, []int{1000}, 2},
   278  		{"FragmentedWithMTUSmallerThanHeaderAndPrependableBytes", 300, &stack.GSO{}, 1000, header.IPv4MinimumSize + 77, []int{500}, 6},
   279  	}
   280  
   281  	for _, ft := range fragTests {
   282  		t.Run(ft.description, func(t *testing.T) {
   283  			hdr, payload := makeHdrAndPayload(ft.hdrLength, ft.extraLength, ft.payloadViewsSizes)
   284  			source := tcpip.PacketBuffer{
   285  				Header: hdr,
   286  				// Save the source payload because WritePacket will modify it.
   287  				Data: payload.Clone(nil),
   288  			}
   289  			c := buildContext(t, nil, ft.mtu)
   290  			err := c.Route.WritePacket(ft.gso, stack.NetworkHeaderParams{Protocol: tcp.ProtocolNumber, TTL: 42, TOS: stack.DefaultTOS}, tcpip.PacketBuffer{
   291  				Header: hdr,
   292  				Data:   payload,
   293  			})
   294  			if err != nil {
   295  				t.Errorf("err got %v, want %v", err, nil)
   296  			}
   297  
   298  			var results []tcpip.PacketBuffer
   299  		L:
   300  			for {
   301  				select {
   302  				case pi := <-c.linkEP.Ch:
   303  					results = append(results, pi)
   304  				default:
   305  					break L
   306  				}
   307  			}
   308  
   309  			if got, want := len(results), ft.expectedFrags; got != want {
   310  				t.Errorf("len(result) got %d, want %d", got, want)
   311  			}
   312  			if got, want := len(results), int(c.Route.Stats().IP.PacketsSent.Value()); got != want {
   313  				t.Errorf("no errors yet len(result) got %d, want %d", got, want)
   314  			}
   315  			compareFragments(t, results, source, ft.mtu)
   316  		})
   317  	}
   318  }
   319  
   320  // TestFragmentationErrors checks that errors are returned from write packet
   321  // correctly.
   322  func TestFragmentationErrors(t *testing.T) {
   323  	fragTests := []struct {
   324  		description           string
   325  		mtu                   uint32
   326  		hdrLength             int
   327  		payloadViewsSizes     []int
   328  		packetCollectorErrors []*tcpip.Error
   329  	}{
   330  		{"NoFrag", 2000, 0, []int{1000}, []*tcpip.Error{tcpip.ErrAborted}},
   331  		{"ErrorOnFirstFrag", 500, 0, []int{1000}, []*tcpip.Error{tcpip.ErrAborted}},
   332  		{"ErrorOnSecondFrag", 500, 0, []int{1000}, []*tcpip.Error{nil, tcpip.ErrAborted}},
   333  		{"ErrorOnFirstFragMTUSmallerThanHdr", 500, 1000, []int{500}, []*tcpip.Error{tcpip.ErrAborted}},
   334  	}
   335  
   336  	for _, ft := range fragTests {
   337  		t.Run(ft.description, func(t *testing.T) {
   338  			hdr, payload := makeHdrAndPayload(ft.hdrLength, header.IPv4MinimumSize, ft.payloadViewsSizes)
   339  			c := buildContext(t, ft.packetCollectorErrors, ft.mtu)
   340  			err := c.Route.WritePacket(&stack.GSO{}, stack.NetworkHeaderParams{Protocol: tcp.ProtocolNumber, TTL: 42, TOS: stack.DefaultTOS}, tcpip.PacketBuffer{
   341  				Header: hdr,
   342  				Data:   payload,
   343  			})
   344  			for i := 0; i < len(ft.packetCollectorErrors)-1; i++ {
   345  				if got, want := ft.packetCollectorErrors[i], (*tcpip.Error)(nil); got != want {
   346  					t.Errorf("ft.packetCollectorErrors[%d] got %v, want %v", i, got, want)
   347  				}
   348  			}
   349  			// We only need to check that last error because all the ones before are
   350  			// nil.
   351  			if got, want := err, ft.packetCollectorErrors[len(ft.packetCollectorErrors)-1]; got != want {
   352  				t.Errorf("err got %v, want %v", got, want)
   353  			}
   354  			if got, want := c.linkEP.Drain(), int(c.Route.Stats().IP.PacketsSent.Value())+1; err != nil && got != want {
   355  				t.Errorf("after linkEP error len(result) got %d, want %d", got, want)
   356  			}
   357  		})
   358  	}
   359  }
   360  
   361  func TestInvalidFragments(t *testing.T) {
   362  	// These packets have both IHL and TotalLength set to 0.
   363  	testCases := []struct {
   364  		name                   string
   365  		packets                [][]byte
   366  		wantMalformedIPPackets uint64
   367  		wantMalformedFragments uint64
   368  	}{
   369  		{
   370  			"ihl_totallen_zero_valid_frag_offset",
   371  			[][]byte{
   372  				{0x40, 0x30, 0x00, 0x00, 0x6c, 0x74, 0x7d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x39, 0x32, 0x39, 0x33, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   373  			},
   374  			1,
   375  			0,
   376  		},
   377  		{
   378  			"ihl_totallen_zero_invalid_frag_offset",
   379  			[][]byte{
   380  				{0x40, 0x30, 0x00, 0x00, 0x6c, 0x74, 0x20, 0x00, 0x30, 0x30, 0x30, 0x30, 0x39, 0x32, 0x39, 0x33, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   381  			},
   382  			1,
   383  			0,
   384  		},
   385  		{
   386  			// Total Length of 37(20 bytes IP header + 17 bytes of
   387  			// payload)
   388  			// Frag Offset of 0x1ffe = 8190*8 = 65520
   389  			// Leading to the fragment end to be past 65535.
   390  			"ihl_totallen_valid_invalid_frag_offset_1",
   391  			[][]byte{
   392  				{0x45, 0x30, 0x00, 0x25, 0x6c, 0x74, 0x1f, 0xfe, 0x30, 0x30, 0x30, 0x30, 0x39, 0x32, 0x39, 0x33, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   393  			},
   394  			1,
   395  			1,
   396  		},
   397  		// The following 3 tests were found by running a fuzzer and were
   398  		// triggering a panic in the IPv4 reassembler code.
   399  		{
   400  			"ihl_less_than_ipv4_minimum_size_1",
   401  			[][]byte{
   402  				{0x42, 0x30, 0x0, 0x30, 0x30, 0x40, 0x0, 0xf3, 0x30, 0x1, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   403  				{0x42, 0x30, 0x0, 0x8, 0x30, 0x40, 0x20, 0x0, 0x30, 0x1, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   404  			},
   405  			2,
   406  			0,
   407  		},
   408  		{
   409  			"ihl_less_than_ipv4_minimum_size_2",
   410  			[][]byte{
   411  				{0x42, 0x30, 0x0, 0x30, 0x30, 0x40, 0xb3, 0x12, 0x30, 0x6, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   412  				{0x42, 0x30, 0x0, 0x8, 0x30, 0x40, 0x20, 0x0, 0x30, 0x6, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   413  			},
   414  			2,
   415  			0,
   416  		},
   417  		{
   418  			"ihl_less_than_ipv4_minimum_size_3",
   419  			[][]byte{
   420  				{0x42, 0x30, 0x0, 0x30, 0x30, 0x40, 0xb3, 0x30, 0x30, 0x6, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   421  				{0x42, 0x30, 0x0, 0x8, 0x30, 0x40, 0x20, 0x0, 0x30, 0x6, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   422  			},
   423  			2,
   424  			0,
   425  		},
   426  		{
   427  			"fragment_with_short_total_len_extra_payload",
   428  			[][]byte{
   429  				{0x46, 0x30, 0x00, 0x30, 0x30, 0x40, 0x0e, 0x12, 0x30, 0x06, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   430  				{0x46, 0x30, 0x00, 0x18, 0x30, 0x40, 0x20, 0x00, 0x30, 0x06, 0x30, 0x30, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30},
   431  			},
   432  			1,
   433  			1,
   434  		},
   435  		{
   436  			"multiple_fragments_with_more_fragments_set_to_false",
   437  			[][]byte{
   438  				{0x45, 0x00, 0x00, 0x1c, 0x30, 0x40, 0x00, 0x10, 0x00, 0x06, 0x34, 0x69, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
   439  				{0x45, 0x00, 0x00, 0x1c, 0x30, 0x40, 0x00, 0x01, 0x61, 0x06, 0x34, 0x69, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
   440  				{0x45, 0x00, 0x00, 0x1c, 0x30, 0x40, 0x20, 0x00, 0x00, 0x06, 0x34, 0x1e, 0x73, 0x73, 0x69, 0x6e, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
   441  			},
   442  			1,
   443  			1,
   444  		},
   445  	}
   446  
   447  	for _, tc := range testCases {
   448  		t.Run(tc.name, func(t *testing.T) {
   449  			const nicID tcpip.NICID = 42
   450  			s := stack.New(stack.Options{
   451  				NetworkProtocols: []stack.NetworkProtocol{
   452  					ipv4.NewProtocol(),
   453  				},
   454  			})
   455  
   456  			var linkAddr = tcpip.LinkAddress([]byte{0x30, 0x30, 0x30, 0x30, 0x30, 0x30})
   457  			var remoteLinkAddr = tcpip.LinkAddress([]byte{0x30, 0x30, 0x30, 0x30, 0x30, 0x31})
   458  			ep := channel.New(10, 1500, linkAddr)
   459  			s.CreateNIC(nicID, sniffer.New(ep))
   460  
   461  			for _, pkt := range tc.packets {
   462  				ep.InjectLinkAddr(header.IPv4ProtocolNumber, remoteLinkAddr, tcpip.PacketBuffer{
   463  					Data: buffer.NewVectorisedView(len(pkt), []buffer.View{pkt}),
   464  				})
   465  			}
   466  
   467  			if got, want := s.Stats().IP.MalformedPacketsReceived.Value(), tc.wantMalformedIPPackets; got != want {
   468  				t.Errorf("incorrect Stats.IP.MalformedPacketsReceived, got: %d, want: %d", got, want)
   469  			}
   470  			if got, want := s.Stats().IP.MalformedFragmentsReceived.Value(), tc.wantMalformedFragments; got != want {
   471  				t.Errorf("incorrect Stats.IP.MalformedFragmentsReceived, got: %d, want: %d", got, want)
   472  			}
   473  		})
   474  	}
   475  }