// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package header import ( "bytes" "errors" "io" "testing" "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" ) // Equal returns true of a and b are equivalent. // // Note, Equal will return true if a and b hold the same Identifier value and // contain the same bytes in Buf, even if the bytes are split across views // differently. // // Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported // fields. func (a IPv6RawPayloadHeader) Equal(b IPv6RawPayloadHeader) bool { return a.Identifier == b.Identifier && bytes.Equal(a.Buf.ToView(), b.Buf.ToView()) } // Equal returns true of a and b are equivalent. // // Note, Equal will return true if a and b hold equivalent ipv6OptionsExtHdrs. // // Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported // fields. func (a IPv6HopByHopOptionsExtHdr) Equal(b IPv6HopByHopOptionsExtHdr) bool { return bytes.Equal(a.ipv6OptionsExtHdr, b.ipv6OptionsExtHdr) } // Equal returns true of a and b are equivalent. // // Note, Equal will return true if a and b hold equivalent ipv6OptionsExtHdrs. // // Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported // fields. func (a IPv6DestinationOptionsExtHdr) Equal(b IPv6DestinationOptionsExtHdr) bool { return bytes.Equal(a.ipv6OptionsExtHdr, b.ipv6OptionsExtHdr) } func TestIPv6UnknownExtHdrOption(t *testing.T) { tests := []struct { name string identifier IPv6ExtHdrOptionIdentifier expectedUnknownAction IPv6OptionUnknownAction }{ { name: "Skip with zero LSBs", identifier: 0, expectedUnknownAction: IPv6OptionUnknownActionSkip, }, { name: "Discard with zero LSBs", identifier: 64, expectedUnknownAction: IPv6OptionUnknownActionDiscard, }, { name: "Discard and ICMP with zero LSBs", identifier: 128, expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMP, }, { name: "Discard and ICMP for non multicast destination with zero LSBs", identifier: 192, expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, }, { name: "Skip with non-zero LSBs", identifier: 63, expectedUnknownAction: IPv6OptionUnknownActionSkip, }, { name: "Discard with non-zero LSBs", identifier: 127, expectedUnknownAction: IPv6OptionUnknownActionDiscard, }, { name: "Discard and ICMP with non-zero LSBs", identifier: 191, expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMP, }, { name: "Discard and ICMP for non multicast destination with non-zero LSBs", identifier: 255, expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { opt := &IPv6UnknownExtHdrOption{Identifier: test.identifier, Data: []byte{1, 2, 3, 4}} if a := opt.UnknownAction(); a != test.expectedUnknownAction { t.Fatalf("got UnknownAction() = %d, want = %d", a, test.expectedUnknownAction) } }) } } func TestIPv6OptionsExtHdrIterErr(t *testing.T) { tests := []struct { name string bytes []byte err error }{ { name: "Single unknown with zero length", bytes: []byte{255, 0}, }, { name: "Single unknown with non-zero length", bytes: []byte{255, 3, 1, 2, 3}, }, { name: "Two options", bytes: []byte{ 255, 0, 254, 1, 1, }, }, { name: "Three options", bytes: []byte{ 255, 0, 254, 1, 1, 253, 4, 2, 3, 4, 5, }, }, { name: "Single unknown only identifier", bytes: []byte{255}, err: io.ErrUnexpectedEOF, }, { name: "Single unknown too small with length = 1", bytes: []byte{255, 1}, err: io.ErrUnexpectedEOF, }, { name: "Single unknown too small with length = 2", bytes: []byte{255, 2, 1}, err: io.ErrUnexpectedEOF, }, { name: "Valid first with second unknown only identifier", bytes: []byte{ 255, 0, 254, }, err: io.ErrUnexpectedEOF, }, { name: "Valid first with second unknown missing data", bytes: []byte{ 255, 0, 254, 1, }, err: io.ErrUnexpectedEOF, }, { name: "Valid first with second unknown too small", bytes: []byte{ 255, 0, 254, 2, 1, }, err: io.ErrUnexpectedEOF, }, { name: "One Pad1", bytes: []byte{0}, }, { name: "Multiple Pad1", bytes: []byte{0, 0, 0}, }, { name: "Multiple PadN", bytes: []byte{ // Pad3 1, 1, 1, // Pad5 1, 3, 1, 2, 3, }, }, { name: "Pad5 too small middle of data buffer", bytes: []byte{1, 3, 1, 2}, err: io.ErrUnexpectedEOF, }, { name: "Pad5 no data", bytes: []byte{1, 3}, err: io.ErrUnexpectedEOF, }, { name: "Router alert without data", bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 0}, err: ErrMalformedIPv6ExtHdrOption, }, { name: "Router alert with partial data", bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1, 1}, err: ErrMalformedIPv6ExtHdrOption, }, { name: "Router alert with partial data and Pad1", bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1, 1, 0}, err: ErrMalformedIPv6ExtHdrOption, }, { name: "Router alert with extra data", bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 3, 1, 2, 3}, err: ErrMalformedIPv6ExtHdrOption, }, { name: "Router alert with missing data", bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1}, err: io.ErrUnexpectedEOF, }, } check := func(t *testing.T, it IPv6OptionsExtHdrOptionsIterator, expectedErr error) { for i := 0; ; i++ { _, done, err := it.Next() if err != nil { // If we encountered a non-nil error while iterating, make sure it is // is the same error as expectedErr. if !errors.Is(err, expectedErr) { t.Fatalf("got %d-th Next() = %v, want = %v", i, err, expectedErr) } return } if done { // If we are done (without an error), make sure that we did not expect // an error. if expectedErr != nil { t.Fatalf("expected error when iterating; want = %s", expectedErr) } return } } } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Run("Hop By Hop", func(t *testing.T) { extHdr := IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: test.bytes} check(t, extHdr.Iter(), test.err) }) t.Run("Destination", func(t *testing.T) { extHdr := IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: test.bytes} check(t, extHdr.Iter(), test.err) }) }) } } func TestIPv6OptionsExtHdrIter(t *testing.T) { tests := []struct { name string bytes []byte expected []IPv6ExtHdrOption }{ { name: "Single unknown with zero length", bytes: []byte{255, 0}, expected: []IPv6ExtHdrOption{ &IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{}}, }, }, { name: "Single unknown with non-zero length", bytes: []byte{255, 3, 1, 2, 3}, expected: []IPv6ExtHdrOption{ &IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{1, 2, 3}}, }, }, { name: "Single Pad1", bytes: []byte{0}, }, { name: "Two Pad1", bytes: []byte{0, 0}, }, { name: "Single Pad3", bytes: []byte{1, 1, 1}, }, { name: "Single Pad5", bytes: []byte{1, 3, 1, 2, 3}, }, { name: "Multiple Pad", bytes: []byte{ // Pad1 0, // Pad2 1, 0, // Pad3 1, 1, 1, // Pad4 1, 2, 1, 2, // Pad5 1, 3, 1, 2, 3, }, }, { name: "Multiple options", bytes: []byte{ // Pad1 0, // Unknown 255, 0, // Pad2 1, 0, // Unknown 254, 1, 1, // Pad3 1, 1, 1, // Unknown 253, 4, 2, 3, 4, 5, // Pad4 1, 2, 1, 2, }, expected: []IPv6ExtHdrOption{ &IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{}}, &IPv6UnknownExtHdrOption{Identifier: 254, Data: []byte{1}}, &IPv6UnknownExtHdrOption{Identifier: 253, Data: []byte{2, 3, 4, 5}}, }, }, } checkIter := func(t *testing.T, it IPv6OptionsExtHdrOptionsIterator, expected []IPv6ExtHdrOption) { for i, e := range expected { opt, done, err := it.Next() if err != nil { t.Errorf("(i=%d) Next(): %s", i, err) } if done { t.Errorf("(i=%d) unexpectedly done iterating", i) } if diff := cmp.Diff(e, opt); diff != "" { t.Errorf("(i=%d) got option mismatch (-want +got):\n%s", i, diff) } if t.Failed() { t.FailNow() } } opt, done, err := it.Next() if err != nil { t.Errorf("(last) Next(): %s", err) } if !done { t.Errorf("(last) iterator unexpectedly not done") } if opt != nil { t.Errorf("(last) got Next() = %T, want = nil", opt) } } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Run("Hop By Hop", func(t *testing.T) { extHdr := IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: test.bytes} checkIter(t, extHdr.Iter(), test.expected) }) t.Run("Destination", func(t *testing.T) { extHdr := IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: test.bytes} checkIter(t, extHdr.Iter(), test.expected) }) }) } } func TestIPv6RoutingExtHdr(t *testing.T) { tests := []struct { name string bytes []byte segmentsLeft uint8 }{ { name: "Zeroes", bytes: []byte{0, 0, 0, 0, 0, 0}, segmentsLeft: 0, }, { name: "Ones", bytes: []byte{1, 1, 1, 1, 1, 1}, segmentsLeft: 1, }, { name: "Mixed", bytes: []byte{1, 2, 3, 4, 5, 6}, segmentsLeft: 2, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { extHdr := IPv6RoutingExtHdr(test.bytes) if got := extHdr.SegmentsLeft(); got != test.segmentsLeft { t.Errorf("got SegmentsLeft() = %d, want = %d", got, test.segmentsLeft) } }) } } func TestIPv6FragmentExtHdr(t *testing.T) { tests := []struct { name string bytes [6]byte fragmentOffset uint16 more bool id uint32 }{ { name: "Zeroes", bytes: [6]byte{0, 0, 0, 0, 0, 0}, fragmentOffset: 0, more: false, id: 0, }, { name: "Ones", bytes: [6]byte{0, 9, 0, 0, 0, 1}, fragmentOffset: 1, more: true, id: 1, }, { name: "Mixed", bytes: [6]byte{68, 9, 128, 4, 2, 1}, fragmentOffset: 2177, more: true, id: 2147746305, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { extHdr := IPv6FragmentExtHdr(test.bytes) if got := extHdr.FragmentOffset(); got != test.fragmentOffset { t.Errorf("got FragmentOffset() = %d, want = %d", got, test.fragmentOffset) } if got := extHdr.More(); got != test.more { t.Errorf("got More() = %t, want = %t", got, test.more) } if got := extHdr.ID(); got != test.id { t.Errorf("got ID() = %d, want = %d", got, test.id) } }) } } func makeVectorisedViewFromByteBuffers(bs ...[]byte) buffer.VectorisedView { size := 0 var vs []buffer.View for _, b := range bs { vs = append(vs, buffer.View(b)) size += len(b) } return buffer.NewVectorisedView(size, vs) } func TestIPv6ExtHdrIterErr(t *testing.T) { tests := []struct { name string firstNextHdr IPv6ExtensionHeaderIdentifier payload buffer.VectorisedView err error }{ { name: "Upper layer only without data", firstNextHdr: 255, }, { name: "Upper layer only with data", firstNextHdr: 255, payload: makeVectorisedViewFromByteBuffers([]byte{1, 2, 3, 4}), }, { name: "No next header", firstNextHdr: IPv6NoNextHeaderIdentifier, }, { name: "No next header with data", firstNextHdr: IPv6NoNextHeaderIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{1, 2, 3, 4}), }, { name: "Valid single hop by hop", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3, 4}), }, { name: "Hop by hop too small", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3}), err: io.ErrUnexpectedEOF, }, { name: "Valid single fragment", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 68, 9, 128, 4, 2, 1}), }, { name: "Fragment too small", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 68, 9, 128, 4, 2}), err: io.ErrUnexpectedEOF, }, { name: "Valid single destination", firstNextHdr: IPv6DestinationOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3, 4}), }, { name: "Destination too small", firstNextHdr: IPv6DestinationOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3}), err: io.ErrUnexpectedEOF, }, { name: "Valid single routing", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2, 3, 4, 5, 6}), }, { name: "Valid single routing across views", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2}, []byte{3, 4, 5, 6}), }, { name: "Routing too small with zero length field", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2, 3, 4, 5}), err: io.ErrUnexpectedEOF, }, { name: "Valid routing with non-zero length field", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8}), }, { name: "Valid routing with non-zero length field across views", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6}, []byte{1, 2, 3, 4, 5, 6, 7, 8}), }, { name: "Routing too small with non-zero length field", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7}), err: io.ErrUnexpectedEOF, }, { name: "Routing too small with non-zero length field across views", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6}, []byte{1, 2, 3, 4, 5, 6, 7}), err: io.ErrUnexpectedEOF, }, { name: "Mixed", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop Options extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // (Atomic) Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1, // Routing extension header. uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Destination Options extension header. 255, 0, 255, 4, 1, 2, 3, 4, // Upper layer data. 1, 2, 3, 4, }), }, { name: "Mixed without upper layer data", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop Options extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // (Atomic) Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1, // Routing extension header. uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Destination Options extension header. 255, 0, 255, 4, 1, 2, 3, 4, }), }, { name: "Mixed without upper layer data but last ext hdr too small", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop Options extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // (Atomic) Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1, // Routing extension header. uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Destination Options extension header. 255, 0, 255, 4, 1, 2, 3, }), err: io.ErrUnexpectedEOF, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { it := MakeIPv6PayloadIterator(test.firstNextHdr, test.payload) for i := 0; ; i++ { _, done, err := it.Next() if err != nil { // If we encountered a non-nil error while iterating, make sure it is // is the same error as test.err. if !errors.Is(err, test.err) { t.Fatalf("got %d-th Next() = %v, want = %v", i, err, test.err) } return } if done { // If we are done (without an error), make sure that we did not expect // an error. if test.err != nil { t.Fatalf("expected error when iterating; want = %s", test.err) } return } } }) } } func TestIPv6ExtHdrIter(t *testing.T) { routingExtHdrWithUpperLayerData := buffer.View([]byte{255, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4}) upperLayerData := buffer.View([]byte{1, 2, 3, 4}) tests := []struct { name string firstNextHdr IPv6ExtensionHeaderIdentifier payload buffer.VectorisedView expected []IPv6PayloadHeader }{ // With a non-atomic fragment that is not the first fragment, the payload // after the fragment will not be parsed because the payload is expected to // only hold upper layer data. { name: "hopbyhop - fragment (not first) - routing - upper", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // Fragment extension header. // // More = 1, Fragment Offset = 2117, ID = 2147746305 uint8(IPv6RoutingExtHdrIdentifier), 0, 68, 9, 128, 4, 2, 1, // Routing extension header. // // Even though we have a routing ext header here, it should be // be interpretted as raw bytes as only the first fragment is expected // to hold headers. 255, 0, 1, 2, 3, 4, 5, 6, // Upper layer data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}}, IPv6FragmentExtHdr([6]byte{68, 9, 128, 4, 2, 1}), IPv6RawPayloadHeader{ Identifier: IPv6RoutingExtHdrIdentifier, Buf: routingExtHdrWithUpperLayerData.ToVectorisedView(), }, }, }, { name: "hopbyhop - fragment (first) - routing - upper", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // Fragment extension header. // // More = 1, Fragment Offset = 0, ID = 2147746305 uint8(IPv6RoutingExtHdrIdentifier), 0, 0, 1, 128, 4, 2, 1, // Routing extension header. 255, 0, 1, 2, 3, 4, 5, 6, // Upper layer data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}}, IPv6FragmentExtHdr([6]byte{0, 1, 128, 4, 2, 1}), IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6RawPayloadHeader{ Identifier: 255, Buf: upperLayerData.ToVectorisedView(), }, }, }, { name: "fragment - routing - upper (across views)", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Fragment extension header. uint8(IPv6RoutingExtHdrIdentifier), 0, 68, 9, 128, 4, 2, 1, // Routing extension header. 255, 0, 1, 2}, []byte{3, 4, 5, 6, // Upper layer data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6FragmentExtHdr([6]byte{68, 9, 128, 4, 2, 1}), IPv6RawPayloadHeader{ Identifier: IPv6RoutingExtHdrIdentifier, Buf: routingExtHdrWithUpperLayerData.ToVectorisedView(), }, }, }, // If we have an atomic fragment, the payload following the fragment // extension header should be parsed normally. { name: "atomic fragment - routing - destination - upper", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1, // Routing extension header. uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Destination Options extension header. 255, 0, 1, 4, 1, 2, 3, 4, // Upper layer data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}), IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}}, IPv6RawPayloadHeader{ Identifier: 255, Buf: upperLayerData.ToVectorisedView(), }, }, }, { name: "atomic fragment - routing - upper (across views)", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6}, []byte{128, 4, 2, 1, // Routing extension header. 255, 0, 1, 2}, []byte{3, 4, 5, 6, // Upper layer data. 1, 2}, []byte{3, 4}), expected: []IPv6PayloadHeader{ IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}), IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6RawPayloadHeader{ Identifier: 255, Buf: makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]), }, }, }, { name: "atomic fragment - destination - no next header", firstNextHdr: IPv6FragmentExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Fragment extension header. // // Res (Reserved) bits are 1 which should not affect anything. uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 0, 6, 128, 4, 2, 1, // Destination Options extension header. uint8(IPv6NoNextHeaderIdentifier), 0, 1, 4, 1, 2, 3, 4, // Random data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}), IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}}, }, }, { name: "routing - atomic fragment - no next header", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Routing extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6NoNextHeaderIdentifier), 0, 0, 6, 128, 4, 2, 1, // Random data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}), }, }, { name: "routing - atomic fragment - no next header (across views)", firstNextHdr: IPv6RoutingExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Routing extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Fragment extension header. // // Reserved bits are 1 which should not affect anything. uint8(IPv6NoNextHeaderIdentifier), 255, 0, 6}, []byte{128, 4, 2, 1, // Random data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}), }, }, { name: "hopbyhop - routing - fragment - no next header", firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier, payload: makeVectorisedViewFromByteBuffers([]byte{ // Hop By Hop Options extension header. uint8(IPv6RoutingExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4, // Routing extension header. uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6, // Fragment extension header. // // Fragment Offset = 32; Res = 6. uint8(IPv6NoNextHeaderIdentifier), 0, 1, 6, 128, 4, 2, 1, // Random data. 1, 2, 3, 4, }), expected: []IPv6PayloadHeader{ IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}}, IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}), IPv6FragmentExtHdr([6]byte{1, 6, 128, 4, 2, 1}), IPv6RawPayloadHeader{ Identifier: IPv6NoNextHeaderIdentifier, Buf: upperLayerData.ToVectorisedView(), }, }, }, // Test the raw payload for common transport layer protocol numbers. { name: "TCP raw payload", firstNextHdr: IPv6ExtensionHeaderIdentifier(TCPProtocolNumber), payload: makeVectorisedViewFromByteBuffers(upperLayerData), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: IPv6ExtensionHeaderIdentifier(TCPProtocolNumber), Buf: upperLayerData.ToVectorisedView(), }}, }, { name: "UDP raw payload", firstNextHdr: IPv6ExtensionHeaderIdentifier(UDPProtocolNumber), payload: makeVectorisedViewFromByteBuffers(upperLayerData), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: IPv6ExtensionHeaderIdentifier(UDPProtocolNumber), Buf: upperLayerData.ToVectorisedView(), }}, }, { name: "ICMPv4 raw payload", firstNextHdr: IPv6ExtensionHeaderIdentifier(ICMPv4ProtocolNumber), payload: makeVectorisedViewFromByteBuffers(upperLayerData), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: IPv6ExtensionHeaderIdentifier(ICMPv4ProtocolNumber), Buf: upperLayerData.ToVectorisedView(), }}, }, { name: "ICMPv6 raw payload", firstNextHdr: IPv6ExtensionHeaderIdentifier(ICMPv6ProtocolNumber), payload: makeVectorisedViewFromByteBuffers(upperLayerData), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: IPv6ExtensionHeaderIdentifier(ICMPv6ProtocolNumber), Buf: upperLayerData.ToVectorisedView(), }}, }, { name: "Unknwon next header raw payload", firstNextHdr: 255, payload: makeVectorisedViewFromByteBuffers(upperLayerData), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: 255, Buf: upperLayerData.ToVectorisedView(), }}, }, { name: "Unknwon next header raw payload (across views)", firstNextHdr: 255, payload: makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]), expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{ Identifier: 255, Buf: makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]), }}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { it := MakeIPv6PayloadIterator(test.firstNextHdr, test.payload) for i, e := range test.expected { extHdr, done, err := it.Next() if err != nil { t.Errorf("(i=%d) Next(): %s", i, err) } if done { t.Errorf("(i=%d) unexpectedly done iterating", i) } if diff := cmp.Diff(e, extHdr); diff != "" { t.Errorf("(i=%d) got ext hdr mismatch (-want +got):\n%s", i, diff) } if t.Failed() { t.FailNow() } } extHdr, done, err := it.Next() if err != nil { t.Errorf("(last) Next(): %s", err) } if !done { t.Errorf("(last) iterator unexpectedly not done") } if extHdr != nil { t.Errorf("(last) got Next() = %T, want = nil", extHdr) } }) } } var _ IPv6SerializableHopByHopOption = (*dummyHbHOptionSerializer)(nil) // dummyHbHOptionSerializer provides a generic implementation of // IPv6SerializableHopByHopOption for use in tests. type dummyHbHOptionSerializer struct { id IPv6ExtHdrOptionIdentifier payload []byte align int alignOffset int } // identifier implements IPv6SerializableHopByHopOption. func (s *dummyHbHOptionSerializer) identifier() IPv6ExtHdrOptionIdentifier { return s.id } // length implements IPv6SerializableHopByHopOption. func (s *dummyHbHOptionSerializer) length() uint8 { return uint8(len(s.payload)) } // alignment implements IPv6SerializableHopByHopOption. func (s *dummyHbHOptionSerializer) alignment() (int, int) { align := 1 if s.align != 0 { align = s.align } return align, s.alignOffset } // serializeInto implements IPv6SerializableHopByHopOption. func (s *dummyHbHOptionSerializer) serializeInto(b []byte) uint8 { return uint8(copy(b, s.payload)) } func TestIPv6HopByHopSerializer(t *testing.T) { validateDummies := func(t *testing.T, serializable IPv6SerializableHopByHopOption, deserialized IPv6ExtHdrOption) { t.Helper() dummy, ok := serializable.(*dummyHbHOptionSerializer) if !ok { t.Fatalf("got serializable = %T, want = *dummyHbHOptionSerializer", serializable) } unknown, ok := deserialized.(*IPv6UnknownExtHdrOption) if !ok { t.Fatalf("got deserialized = %T, want = %T", deserialized, &IPv6UnknownExtHdrOption{}) } if dummy.id != unknown.Identifier { t.Errorf("got deserialized identifier = %d, want = %d", unknown.Identifier, dummy.id) } if diff := cmp.Diff(dummy.payload, unknown.Data); diff != "" { t.Errorf("option payload deserialization mismatch (-want +got):\n%s", diff) } } tests := []struct { name string nextHeader uint8 options []IPv6SerializableHopByHopOption expect []byte validate func(*testing.T, IPv6SerializableHopByHopOption, IPv6ExtHdrOption) }{ { name: "single option", nextHeader: 13, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 15, payload: []byte{9, 8, 7, 6}, }, }, expect: []byte{13, 0, 15, 4, 9, 8, 7, 6}, validate: validateDummies, }, { name: "short option padN zero", nextHeader: 88, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 22, payload: []byte{4, 5}, }, }, expect: []byte{88, 0, 22, 2, 4, 5, 1, 0}, validate: validateDummies, }, { name: "short option pad1", nextHeader: 11, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 33, payload: []byte{1, 2, 3}, }, }, expect: []byte{11, 0, 33, 3, 1, 2, 3, 0}, validate: validateDummies, }, { name: "long option padN", nextHeader: 55, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 77, payload: []byte{1, 2, 3, 4, 5, 6, 7, 8}, }, }, expect: []byte{55, 1, 77, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 0, 0}, validate: validateDummies, }, { name: "two options", nextHeader: 33, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 11, payload: []byte{1, 2, 3}, }, &dummyHbHOptionSerializer{ id: 22, payload: []byte{4, 5, 6}, }, }, expect: []byte{33, 1, 11, 3, 1, 2, 3, 22, 3, 4, 5, 6, 1, 2, 0, 0}, validate: validateDummies, }, { name: "two options align 2n", nextHeader: 33, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 11, payload: []byte{1, 2, 3}, }, &dummyHbHOptionSerializer{ id: 22, payload: []byte{4, 5, 6}, align: 2, }, }, expect: []byte{33, 1, 11, 3, 1, 2, 3, 0, 22, 3, 4, 5, 6, 1, 1, 0}, validate: validateDummies, }, { name: "two options align 8n+1", nextHeader: 33, options: []IPv6SerializableHopByHopOption{ &dummyHbHOptionSerializer{ id: 11, payload: []byte{1, 2}, }, &dummyHbHOptionSerializer{ id: 22, payload: []byte{4, 5, 6}, align: 8, alignOffset: 1, }, }, expect: []byte{33, 1, 11, 2, 1, 2, 1, 1, 0, 22, 3, 4, 5, 6, 1, 0}, validate: validateDummies, }, { name: "no options", nextHeader: 33, options: []IPv6SerializableHopByHopOption{}, expect: []byte{33, 0, 1, 4, 0, 0, 0, 0}, }, { name: "Router Alert", nextHeader: 33, options: []IPv6SerializableHopByHopOption{&IPv6RouterAlertOption{Value: IPv6RouterAlertMLD}}, expect: []byte{33, 0, 5, 2, 0, 0, 1, 0}, validate: func(t *testing.T, _ IPv6SerializableHopByHopOption, deserialized IPv6ExtHdrOption) { t.Helper() routerAlert, ok := deserialized.(*IPv6RouterAlertOption) if !ok { t.Fatalf("got deserialized = %T, want = *IPv6RouterAlertOption", deserialized) } if routerAlert.Value != IPv6RouterAlertMLD { t.Errorf("got routerAlert.Value = %d, want = %d", routerAlert.Value, IPv6RouterAlertMLD) } }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { s := IPv6SerializableHopByHopExtHdr(test.options) length := s.length() if length != len(test.expect) { t.Fatalf("got s.length() = %d, want = %d", length, len(test.expect)) } b := make([]byte, length) for i := range b { // Fill the buffer with ones to ensure all padding is correctly set. b[i] = 0xFF } if got := s.serializeInto(test.nextHeader, b); got != length { t.Fatalf("got s.serializeInto(..) = %d, want = %d", got, length) } if diff := cmp.Diff(test.expect, b); diff != "" { t.Fatalf("serialization mismatch (-want +got):\n%s", diff) } // Deserialize the options and verify them. optLen := (b[ipv6HopByHopExtHdrLengthOffset] + ipv6HopByHopExtHdrUnaccountedLenWords) * ipv6ExtHdrLenBytesPerUnit iter := ipv6OptionsExtHdr(b[ipv6HopByHopExtHdrOptionsOffset:optLen]).Iter() for _, testOpt := range test.options { opt, done, err := iter.Next() if err != nil { t.Fatalf("iter.Next(): %s", err) } if done { t.Fatalf("got iter.Next() = (%T, %t, _), want = (_, false, _)", opt, done) } test.validate(t, testOpt, opt) } opt, done, err := iter.Next() if err != nil { t.Fatalf("iter.Next(): %s", err) } if !done { t.Fatalf("got iter.Next() = (%T, %t, _), want = (_, true, _)", opt, done) } }) } } var _ IPv6SerializableExtHdr = (*dummyIPv6ExtHdrSerializer)(nil) // dummyIPv6ExtHdrSerializer provides a generic implementation of // IPv6SerializableExtHdr for use in tests. // // The dummy header always carries the nextHeader value in the first byte. type dummyIPv6ExtHdrSerializer struct { id IPv6ExtensionHeaderIdentifier headerContents []byte } // identifier implements IPv6SerializableExtHdr. func (s *dummyIPv6ExtHdrSerializer) identifier() IPv6ExtensionHeaderIdentifier { return s.id } // length implements IPv6SerializableExtHdr. func (s *dummyIPv6ExtHdrSerializer) length() int { return len(s.headerContents) + 1 } // serializeInto implements IPv6SerializableExtHdr. func (s *dummyIPv6ExtHdrSerializer) serializeInto(nextHeader uint8, b []byte) int { b[0] = nextHeader return copy(b[1:], s.headerContents) + 1 } func TestIPv6ExtHdrSerializer(t *testing.T) { tests := []struct { name string headers []IPv6SerializableExtHdr nextHeader tcpip.TransportProtocolNumber expectSerialized []byte expectNextHeader uint8 }{ { name: "one header", headers: []IPv6SerializableExtHdr{ &dummyIPv6ExtHdrSerializer{ id: 15, headerContents: []byte{1, 2, 3, 4}, }, }, nextHeader: TCPProtocolNumber, expectSerialized: []byte{byte(TCPProtocolNumber), 1, 2, 3, 4}, expectNextHeader: 15, }, { name: "two headers", headers: []IPv6SerializableExtHdr{ &dummyIPv6ExtHdrSerializer{ id: 22, headerContents: []byte{1, 2, 3}, }, &dummyIPv6ExtHdrSerializer{ id: 23, headerContents: []byte{4, 5, 6}, }, }, nextHeader: ICMPv6ProtocolNumber, expectSerialized: []byte{ 23, 1, 2, 3, byte(ICMPv6ProtocolNumber), 4, 5, 6, }, expectNextHeader: 22, }, { name: "no headers", headers: []IPv6SerializableExtHdr{}, nextHeader: UDPProtocolNumber, expectSerialized: []byte{}, expectNextHeader: byte(UDPProtocolNumber), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { s := IPv6ExtHdrSerializer(test.headers) l := s.Length() if got, want := l, len(test.expectSerialized); got != want { t.Fatalf("got serialized length = %d, want = %d", got, want) } b := make([]byte, l) for i := range b { // Fill the buffer with garbage to make sure we're writing to all bytes. b[i] = 0xFF } nextHeader, serializedLen := s.Serialize(test.nextHeader, b) if serializedLen != len(test.expectSerialized) || nextHeader != test.expectNextHeader { t.Errorf( "got s.Serialize(..) = (%d, %d), want = (%d, %d)", nextHeader, serializedLen, test.expectNextHeader, len(test.expectSerialized), ) } if diff := cmp.Diff(test.expectSerialized, b); diff != "" { t.Errorf("serialization mismatch (-want +got):\n%s", diff) } }) } }