// Copyright 2019 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" "encoding/binary" "errors" "fmt" "io" "regexp" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/testutil" ) // TestNDPNeighborSolicit tests the functions of NDPNeighborSolicit. func TestNDPNeighborSolicit(t *testing.T) { b := []byte{ 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, } // Test getting the Target Address. ns := NDPNeighborSolicit(b) addr := testutil.MustParse6("102:304:506:708:90a:b0c:d0e:f10") if got := ns.TargetAddress(); got != addr { t.Errorf("got ns.TargetAddress = %s, want %s", got, addr) } // Test updating the Target Address. addr2 := testutil.MustParse6("1112:1314:1516:1718:191a:1b1c:1d1e:1f11") ns.SetTargetAddress(addr2) if got := ns.TargetAddress(); got != addr2 { t.Errorf("got ns.TargetAddress = %s, want %s", got, addr2) } // Make sure the address got updated in the backing buffer. if got := tcpip.Address(b[ndpNSTargetAddessOffset:][:IPv6AddressSize]); got != addr2 { t.Errorf("got targetaddress buffer = %s, want %s", got, addr2) } } func TestNDPRouteInformationOption(t *testing.T) { tests := []struct { name string length uint8 prefixLength uint8 prf NDPRoutePreference lifetimeS uint32 prefixBytes []byte expectedPrefix tcpip.Subnet expectedErr error }{ { name: "Length=1 with Prefix Length = 0", length: 1, prefixLength: 0, prf: MediumRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: IPv6EmptySubnet, }, { name: "Length=1 but Prefix Length > 0", length: 1, prefixLength: 1, prf: MediumRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedErr: ErrNDPOptMalformedBody, }, { name: "Length=2 with Prefix Length = 0", length: 2, prefixLength: 0, prf: MediumRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: IPv6EmptySubnet, }, { name: "Length=2 with Prefix Length in [1, 64] (1)", length: 2, prefixLength: 1, prf: LowRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 1, }.Subnet(), }, { name: "Length=2 with Prefix Length in [1, 64] (64)", length: 2, prefixLength: 64, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 64, }.Subnet(), }, { name: "Length=2 with Prefix Length > 64", length: 2, prefixLength: 65, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedErr: ErrNDPOptMalformedBody, }, { name: "Length=3 with Prefix Length = 0", length: 3, prefixLength: 0, prf: MediumRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: IPv6EmptySubnet, }, { name: "Length=3 with Prefix Length in [1, 64] (1)", length: 3, prefixLength: 1, prf: LowRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 1, }.Subnet(), }, { name: "Length=3 with Prefix Length in [1, 64] (64)", length: 3, prefixLength: 64, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 64, }.Subnet(), }, { name: "Length=3 with Prefix Length in [65, 128] (65)", length: 3, prefixLength: 65, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 65, }.Subnet(), }, { name: "Length=3 with Prefix Length in [65, 128] (128)", length: 3, prefixLength: 128, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)), PrefixLen: 128, }.Subnet(), }, { name: "Length=3 with (invalid) Prefix Length > 128", length: 3, prefixLength: 129, prf: HighRoutePreference, lifetimeS: 1, prefixBytes: nil, expectedErr: ErrNDPOptMalformedBody, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { expectedRouteInformationBytes := [...]byte{ // Type, Length 24, test.length, // Prefix Length, Prf uint8(test.prefixLength), uint8(test.prf) << 3, // Route Lifetime 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } binary.BigEndian.PutUint32(expectedRouteInformationBytes[4:], test.lifetimeS) _ = copy(expectedRouteInformationBytes[8:], test.prefixBytes) opts := NDPOptions(expectedRouteInformationBytes[:test.length*lengthByteUnits]) it, err := opts.Iter(false) if err != nil { t.Fatalf("got Iter(false) = (_, %s), want = (_, nil)", err) } opt, done, err := it.Next() if !errors.Is(err, test.expectedErr) { t.Fatalf("got Next() = (_, _, %s), want = (_, _, %s)", err, test.expectedErr) } if want := test.expectedErr != nil; done != want { t.Fatalf("got Next() = (_, %t, _), want = (_, %t, _)", done, want) } if test.expectedErr != nil { return } if got := opt.kind(); got != ndpRouteInformationType { t.Errorf("got kind() = %d, want = %d", got, ndpRouteInformationType) } ri, ok := opt.(NDPRouteInformation) if !ok { t.Fatalf("got opt = %T, want = NDPRouteInformation", opt) } if got := ri.PrefixLength(); got != test.prefixLength { t.Errorf("got PrefixLength() = %d, want = %d", got, test.prefixLength) } if got := ri.RoutePreference(); got != test.prf { t.Errorf("got RoutePreference() = %d, want = %d", got, test.prf) } if got, want := ri.RouteLifetime(), time.Duration(test.lifetimeS)*time.Second; got != want { t.Errorf("got RouteLifetime() = %s, want = %s", got, want) } if got, err := ri.Prefix(); err != nil { t.Errorf("Prefix(): %s", err) } else if got != test.expectedPrefix { t.Errorf("got Prefix() = %s, want = %s", got, test.expectedPrefix) } // Iterator should not return anything else. { next, done, err := it.Next() if err != nil { t.Errorf("got Next() = (_, _, %s), want = (_, _, nil)", err) } if !done { t.Error("got Next() = (_, false, _), want = (_, true, _)") } if next != nil { t.Errorf("got Next() = (%x, _, _), want = (nil, _, _)", next) } } }) } } // TestNDPNeighborAdvert tests the functions of NDPNeighborAdvert. func TestNDPNeighborAdvert(t *testing.T) { b := []byte{ 160, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, } // Test getting the Target Address. na := NDPNeighborAdvert(b) addr := testutil.MustParse6("102:304:506:708:90a:b0c:d0e:f10") if got := na.TargetAddress(); got != addr { t.Errorf("got TargetAddress = %s, want %s", got, addr) } // Test getting the Router Flag. if got := na.RouterFlag(); !got { t.Errorf("got RouterFlag = false, want = true") } // Test getting the Solicited Flag. if got := na.SolicitedFlag(); got { t.Errorf("got SolicitedFlag = true, want = false") } // Test getting the Override Flag. if got := na.OverrideFlag(); !got { t.Errorf("got OverrideFlag = false, want = true") } // Test updating the Target Address. addr2 := testutil.MustParse6("1112:1314:1516:1718:191a:1b1c:1d1e:1f11") na.SetTargetAddress(addr2) if got := na.TargetAddress(); got != addr2 { t.Errorf("got TargetAddress = %s, want %s", got, addr2) } // Make sure the address got updated in the backing buffer. if got := tcpip.Address(b[ndpNATargetAddressOffset:][:IPv6AddressSize]); got != addr2 { t.Errorf("got targetaddress buffer = %s, want %s", got, addr2) } // Test updating the Router Flag. na.SetRouterFlag(false) if got := na.RouterFlag(); got { t.Errorf("got RouterFlag = true, want = false") } // Test updating the Solicited Flag. na.SetSolicitedFlag(true) if got := na.SolicitedFlag(); !got { t.Errorf("got SolicitedFlag = false, want = true") } // Test updating the Override Flag. na.SetOverrideFlag(false) if got := na.OverrideFlag(); got { t.Errorf("got OverrideFlag = true, want = false") } // Make sure flags got updated in the backing buffer. if got := b[ndpNAFlagsOffset]; got != 64 { t.Errorf("got flags byte = %d, want = 64", got) } } func TestNDPRouterAdvert(t *testing.T) { tests := []struct { hopLimit uint8 managedFlag, otherConfFlag bool prf NDPRoutePreference routerLifetimeS uint16 reachableTimeMS, retransTimerMS uint32 }{ { hopLimit: 1, managedFlag: false, otherConfFlag: true, prf: HighRoutePreference, routerLifetimeS: 2, reachableTimeMS: 3, retransTimerMS: 4, }, { hopLimit: 64, managedFlag: true, otherConfFlag: false, prf: LowRoutePreference, routerLifetimeS: 258, reachableTimeMS: 78492, retransTimerMS: 13213, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { flags := uint8(0) if test.managedFlag { flags |= 1 << 7 } if test.otherConfFlag { flags |= 1 << 6 } flags |= uint8(test.prf) << 3 b := []byte{ test.hopLimit, flags, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, } binary.BigEndian.PutUint16(b[2:], test.routerLifetimeS) binary.BigEndian.PutUint32(b[4:], test.reachableTimeMS) binary.BigEndian.PutUint32(b[8:], test.retransTimerMS) ra := NDPRouterAdvert(b) if got := ra.CurrHopLimit(); got != test.hopLimit { t.Errorf("got ra.CurrHopLimit() = %d, want = %d", got, test.hopLimit) } if got := ra.ManagedAddrConfFlag(); got != test.managedFlag { t.Errorf("got ManagedAddrConfFlag() = %t, want = %t", got, test.managedFlag) } if got := ra.OtherConfFlag(); got != test.otherConfFlag { t.Errorf("got OtherConfFlag() = %t, want = %t", got, test.otherConfFlag) } if got := ra.DefaultRouterPreference(); got != test.prf { t.Errorf("got DefaultRouterPreference() = %d, want = %d", got, test.prf) } if got, want := ra.RouterLifetime(), time.Second*time.Duration(test.routerLifetimeS); got != want { t.Errorf("got ra.RouterLifetime() = %d, want = %d", got, want) } if got, want := ra.ReachableTime(), time.Millisecond*time.Duration(test.reachableTimeMS); got != want { t.Errorf("got ra.ReachableTime() = %d, want = %d", got, want) } if got, want := ra.RetransTimer(), time.Millisecond*time.Duration(test.retransTimerMS); got != want { t.Errorf("got ra.RetransTimer() = %d, want = %d", got, want) } }) } } // TestNDPSourceLinkLayerAddressOptionEthernetAddress tests getting the // Ethernet address from an NDPSourceLinkLayerAddressOption. func TestNDPSourceLinkLayerAddressOptionEthernetAddress(t *testing.T) { tests := []struct { name string buf []byte expected tcpip.LinkAddress }{ { "ValidMAC", []byte{1, 2, 3, 4, 5, 6}, tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"), }, { "SLLBodyTooShort", []byte{1, 2, 3, 4, 5}, tcpip.LinkAddress([]byte(nil)), }, { "SLLBodyLargerThanNeeded", []byte{1, 2, 3, 4, 5, 6, 7, 8}, tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { sll := NDPSourceLinkLayerAddressOption(test.buf) if got := sll.EthernetAddress(); got != test.expected { t.Errorf("got sll.EthernetAddress = %s, want = %s", got, test.expected) } }) } } // TestNDPTargetLinkLayerAddressOptionEthernetAddress tests getting the // Ethernet address from an NDPTargetLinkLayerAddressOption. func TestNDPTargetLinkLayerAddressOptionEthernetAddress(t *testing.T) { tests := []struct { name string buf []byte expected tcpip.LinkAddress }{ { "ValidMAC", []byte{1, 2, 3, 4, 5, 6}, tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"), }, { "TLLBodyTooShort", []byte{1, 2, 3, 4, 5}, tcpip.LinkAddress([]byte(nil)), }, { "TLLBodyLargerThanNeeded", []byte{1, 2, 3, 4, 5, 6, 7, 8}, tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { tll := NDPTargetLinkLayerAddressOption(test.buf) if got := tll.EthernetAddress(); got != test.expected { t.Errorf("got tll.EthernetAddress = %s, want = %s", got, test.expected) } }) } } func TestOpts(t *testing.T) { const optionHeaderLen = 2 checkNonce := func(expectedNonce []byte) func(*testing.T, NDPOption) { return func(t *testing.T, opt NDPOption) { if got := opt.kind(); got != ndpNonceOptionType { t.Errorf("got kind() = %d, want = %d", got, ndpNonceOptionType) } nonce, ok := opt.(NDPNonceOption) if !ok { t.Fatalf("got nonce = %T, want = NDPNonceOption", opt) } if diff := cmp.Diff(expectedNonce, nonce.Nonce()); diff != "" { t.Errorf("nonce mismatch (-want +got):\n%s", diff) } } } checkTLL := func(expectedAddr tcpip.LinkAddress) func(*testing.T, NDPOption) { return func(t *testing.T, opt NDPOption) { if got := opt.kind(); got != ndpTargetLinkLayerAddressOptionType { t.Errorf("got kind() = %d, want = %d", got, ndpTargetLinkLayerAddressOptionType) } tll, ok := opt.(NDPTargetLinkLayerAddressOption) if !ok { t.Fatalf("got tll = %T, want = NDPTargetLinkLayerAddressOption", opt) } if got, want := tll.EthernetAddress(), expectedAddr; got != want { t.Errorf("got tll.EthernetAddress = %s, want = %s", got, want) } } } checkSLL := func(expectedAddr tcpip.LinkAddress) func(*testing.T, NDPOption) { return func(t *testing.T, opt NDPOption) { if got := opt.kind(); got != ndpSourceLinkLayerAddressOptionType { t.Errorf("got kind() = %d, want = %d", got, ndpSourceLinkLayerAddressOptionType) } sll, ok := opt.(NDPSourceLinkLayerAddressOption) if !ok { t.Fatalf("got sll = %T, want = NDPSourceLinkLayerAddressOption", opt) } if got, want := sll.EthernetAddress(), expectedAddr; got != want { t.Errorf("got sll.EthernetAddress = %s, want = %s", got, want) } } } const validLifetimeSeconds = 16909060 address := testutil.MustParse6("90a:b0c:d0e:f10:1112:1314:1516:1718") expectedRDNSSBytes := [...]byte{ // Type, Length 25, 3, // Reserved 0, 0, // Lifetime 1, 2, 4, 8, // Address 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, } binary.BigEndian.PutUint32(expectedRDNSSBytes[4:], validLifetimeSeconds) if n := copy(expectedRDNSSBytes[8:], address); n != IPv6AddressSize { t.Fatalf("got copy(...) = %d, want = %d", n, IPv6AddressSize) } // Update reserved fields to non zero values to make sure serializing sets // them to zero. rdnssBytes := expectedRDNSSBytes rdnssBytes[1] = 1 rdnssBytes[2] = 2 const searchListPaddingBytes = 3 const domainName = "abc.abcd.e" expectedSearchListBytes := [...]byte{ // Type, Length 31, 3, // Reserved 0, 0, // Lifetime 1, 0, 0, 0, // Domain names 3, 'a', 'b', 'c', 4, 'a', 'b', 'c', 'd', 1, 'e', 0, 0, 0, 0, 0, } binary.BigEndian.PutUint32(expectedSearchListBytes[4:], validLifetimeSeconds) // Update reserved fields to non zero values to make sure serializing sets // them to zero. searchListBytes := expectedSearchListBytes searchListBytes[2] = 1 searchListBytes[3] = 2 const prefixLength = 43 const onLinkFlag = false const slaacFlag = true const preferredLifetimeSeconds = 84281096 const onLinkFlagBit = 7 const slaacFlagBit = 6 boolToByte := func(v bool) byte { if v { return 1 } return 0 } flags := boolToByte(onLinkFlag)< 0 { next, done, err := it.Next() if err != nil { t.Fatalf("got Next() = (_, _, %s), want = (_, _, nil)", err) } if done { t.Fatal("got Next() = (_, true, _), want = (_, false, _)") } test.check(t, next) } // Iterator should not return anything else. next, done, err := it.Next() if err != nil { t.Errorf("got Next() = (_, _, %s), want = (_, _, nil)", err) } if !done { t.Error("got Next() = (_, false, _), want = (_, true, _)") } if next != nil { t.Errorf("got Next() = (%x, _, _), want = (nil, _, _)", next) } }) } } func TestNDPRecursiveDNSServerOption(t *testing.T) { tests := []struct { name string buf []byte lifetime time.Duration addrs []tcpip.Address }{ { "Valid1Addr", []byte{ 25, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, }, 0, []tcpip.Address{ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", }, }, { "Valid2Addr", []byte{ 25, 5, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, }, 0, []tcpip.Address{ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", "\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x10", }, }, { "Valid3Addr", []byte{ 25, 7, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, }, 0, []tcpip.Address{ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", "\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x10", "\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x11", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { opts := NDPOptions(test.buf) it, err := opts.Iter(true) if err != nil { t.Fatalf("got Iter = (_, %s), want = (_, nil)", err) } // Iterator should get our option. next, done, err := it.Next() if err != nil { t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err) } if done { t.Fatal("got Next = (_, true, _), want = (_, false, _)") } if got := next.kind(); got != ndpRecursiveDNSServerOptionType { t.Fatalf("got Type = %d, want = %d", got, ndpRecursiveDNSServerOptionType) } opt, ok := next.(NDPRecursiveDNSServer) if !ok { t.Fatalf("next (type = %T) cannot be casted to an NDPRecursiveDNSServer", next) } if got := opt.Lifetime(); got != test.lifetime { t.Errorf("got Lifetime = %d, want = %d", got, test.lifetime) } addrs, err := opt.Addresses() if err != nil { t.Errorf("opt.Addresses() = %s", err) } if diff := cmp.Diff(addrs, test.addrs); diff != "" { t.Errorf("mismatched addresses (-want +got):\n%s", diff) } // Iterator should not return anything else. next, done, err = it.Next() if err != nil { t.Errorf("got Next = (_, _, %s), want = (_, _, nil)", err) } if !done { t.Error("got Next = (_, false, _), want = (_, true, _)") } if next != nil { t.Errorf("got Next = (%x, _, _), want = (nil, _, _)", next) } }) } } // TestNDPDNSSearchListOption tests the getters of NDPDNSSearchList. func TestNDPDNSSearchListOption(t *testing.T) { tests := []struct { name string buf []byte lifetime time.Duration domainNames []string err error }{ { name: "Valid1Label", buf: []byte{ 0, 0, 0, 0, 0, 1, 3, 'a', 'b', 'c', 0, 0, 0, 0, }, lifetime: time.Second, domainNames: []string{ "abc", }, err: nil, }, { name: "Valid2Label", buf: []byte{ 0, 0, 0, 0, 0, 5, 3, 'a', 'b', 'c', 4, 'a', 'b', 'c', 'd', 0, 0, 0, 0, 0, 0, 0, }, lifetime: 5 * time.Second, domainNames: []string{ "abc.abcd", }, err: nil, }, { name: "Valid3Label", buf: []byte{ 0, 0, 1, 0, 0, 0, 3, 'a', 'b', 'c', 4, 'a', 'b', 'c', 'd', 1, 'e', 0, 0, 0, 0, 0, }, lifetime: 16777216 * time.Second, domainNames: []string{ "abc.abcd.e", }, err: nil, }, { name: "Valid2Domains", buf: []byte{ 0, 0, 1, 2, 3, 4, 3, 'a', 'b', 'c', 0, 2, 'd', 'e', 3, 'x', 'y', 'z', 0, 0, 0, 0, }, lifetime: 16909060 * time.Second, domainNames: []string{ "abc", "de.xyz", }, err: nil, }, { name: "Valid3DomainsMixedCase", buf: []byte{ 0, 0, 0, 0, 0, 0, 3, 'a', 'B', 'c', 0, 2, 'd', 'E', 3, 'X', 'y', 'z', 0, 1, 'J', 0, }, lifetime: 0, domainNames: []string{ "abc", "de.xyz", "j", }, err: nil, }, { name: "ValidDomainAfterNULL", buf: []byte{ 0, 0, 0, 0, 0, 0, 3, 'a', 'B', 'c', 0, 0, 0, 0, 2, 'd', 'E', 3, 'X', 'y', 'z', 0, }, lifetime: 0, domainNames: []string{ "abc", "de.xyz", }, err: nil, }, { name: "Valid0Domains", buf: []byte{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, lifetime: 0, domainNames: nil, err: nil, }, { name: "NoTrailingNull", buf: []byte{ 0, 0, 0, 0, 0, 0, 7, 'a', 'b', 'c', 'd', 'e', 'f', 'g', }, lifetime: 0, domainNames: nil, err: io.ErrUnexpectedEOF, }, { name: "IncorrectLength", buf: []byte{ 0, 0, 0, 0, 0, 0, 8, 'a', 'b', 'c', 'd', 'e', 'f', 'g', }, lifetime: 0, domainNames: nil, err: io.ErrUnexpectedEOF, }, { name: "IncorrectLengthWithNULL", buf: []byte{ 0, 0, 0, 0, 0, 0, 7, 'a', 'b', 'c', 'd', 'e', 'f', 0, }, lifetime: 0, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "LabelOfLength63", buf: []byte{ 0, 0, 0, 0, 0, 0, 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 0, }, lifetime: 0, domainNames: []string{ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk", }, err: nil, }, { name: "LabelOfLength64", buf: []byte{ 0, 0, 0, 0, 0, 0, 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 0, }, lifetime: 0, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "DomainNameOfLength255", buf: []byte{ 0, 0, 0, 0, 0, 0, 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 62, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 0, }, lifetime: 0, domainNames: []string{ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij", }, err: nil, }, { name: "DomainNameOfLength256", buf: []byte{ 0, 0, 0, 0, 0, 0, 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 0, }, lifetime: 0, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "StartingDigitForLabel", buf: []byte{ 0, 0, 0, 0, 0, 1, 3, '9', 'b', 'c', 0, 0, 0, 0, }, lifetime: time.Second, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "StartingHyphenForLabel", buf: []byte{ 0, 0, 0, 0, 0, 1, 3, '-', 'b', 'c', 0, 0, 0, 0, }, lifetime: time.Second, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "EndingHyphenForLabel", buf: []byte{ 0, 0, 0, 0, 0, 1, 3, 'a', 'b', '-', 0, 0, 0, 0, }, lifetime: time.Second, domainNames: nil, err: ErrNDPOptMalformedBody, }, { name: "EndingDigitForLabel", buf: []byte{ 0, 0, 0, 0, 0, 1, 3, 'a', 'b', '9', 0, 0, 0, 0, }, lifetime: time.Second, domainNames: []string{ "ab9", }, err: nil, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { opt := NDPDNSSearchList(test.buf) if got := opt.Lifetime(); got != test.lifetime { t.Errorf("got Lifetime = %d, want = %d", got, test.lifetime) } domainNames, err := opt.DomainNames() if !errors.Is(err, test.err) { t.Errorf("opt.DomainNames() = %s", err) } if diff := cmp.Diff(domainNames, test.domainNames); diff != "" { t.Errorf("mismatched domain names (-want +got):\n%s", diff) } }) } } func TestNDPSearchListOptionDomainNameLabelInvalidSymbols(t *testing.T) { for r := rune(0); r <= 255; r++ { t.Run(fmt.Sprintf("RuneVal=%d", r), func(t *testing.T) { buf := []byte{ 0, 0, 0, 0, 0, 0, 3, 'a', 0 /* will be replaced */, 'c', 0, 0, 0, 0, } buf[8] = uint8(r) opt := NDPDNSSearchList(buf) // As per RFC 1035 section 2.3.1, the label must only include ASCII // letters, digits and hyphens (a-z, A-Z, 0-9, -). var expectedErr error re := regexp.MustCompile(`[a-zA-Z0-9-]`) if !re.Match([]byte{byte(r)}) { expectedErr = ErrNDPOptMalformedBody } if domainNames, err := opt.DomainNames(); !errors.Is(err, expectedErr) { t.Errorf("got opt.DomainNames() = (%s, %v), want = (_, %v)", domainNames, err, ErrNDPOptMalformedBody) } }) } } // TestNDPOptionsIterCheck tests that Iter will return false if the NDPOptions // the iterator was returned for is malformed. func TestNDPOptionsIterCheck(t *testing.T) { tests := []struct { name string buf []byte expectedErr error }{ { name: "ZeroLengthField", buf: []byte{0, 0, 0, 0, 0, 0, 0, 0}, expectedErr: ErrNDPOptMalformedHeader, }, { name: "ValidSourceLinkLayerAddressOption", buf: []byte{1, 1, 1, 2, 3, 4, 5, 6}, expectedErr: nil, }, { name: "TooSmallSourceLinkLayerAddressOption", buf: []byte{1, 1, 1, 2, 3, 4, 5}, expectedErr: io.ErrUnexpectedEOF, }, { name: "ValidTargetLinkLayerAddressOption", buf: []byte{2, 1, 1, 2, 3, 4, 5, 6}, expectedErr: nil, }, { name: "TooSmallTargetLinkLayerAddressOption", buf: []byte{2, 1, 1, 2, 3, 4, 5}, expectedErr: io.ErrUnexpectedEOF, }, { name: "ValidPrefixInformation", buf: []byte{ 3, 4, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }, expectedErr: nil, }, { name: "TooSmallPrefixInformation", buf: []byte{ 3, 4, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, }, expectedErr: io.ErrUnexpectedEOF, }, { name: "InvalidPrefixInformationLength", buf: []byte{ 3, 3, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, }, expectedErr: ErrNDPOptMalformedBody, }, { name: "ValidSourceAndTargetLinkLayerAddressWithPrefixInformation", buf: []byte{ // Source Link-Layer Address. 1, 1, 1, 2, 3, 4, 5, 6, // Target Link-Layer Address. 2, 1, 7, 8, 9, 10, 11, 12, // Prefix information. 3, 4, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }, expectedErr: nil, }, { name: "ValidSourceAndTargetLinkLayerAddressWithPrefixInformationWithUnrecognized", buf: []byte{ // Source Link-Layer Address. 1, 1, 1, 2, 3, 4, 5, 6, // Target Link-Layer Address. 2, 1, 7, 8, 9, 10, 11, 12, // 255 is an unrecognized type. If 255 ends up // being the type for some recognized type, // update 255 to some other unrecognized value. 255, 2, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, // Prefix information. 3, 4, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, }, expectedErr: nil, }, { name: "InvalidRecursiveDNSServerCutsOffAddress", buf: []byte{ 25, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 3, 4, 5, 6, 7, }, expectedErr: ErrNDPOptMalformedBody, }, { name: "InvalidRecursiveDNSServerInvalidLengthField", buf: []byte{ 25, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, }, expectedErr: io.ErrUnexpectedEOF, }, { name: "RecursiveDNSServerTooSmall", buf: []byte{ 25, 1, 0, 0, 0, 0, 0, }, expectedErr: io.ErrUnexpectedEOF, }, { name: "RecursiveDNSServerMulticast", buf: []byte{ 25, 3, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, }, expectedErr: ErrNDPOptMalformedBody, }, { name: "RecursiveDNSServerUnspecified", buf: []byte{ 25, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, expectedErr: ErrNDPOptMalformedBody, }, { name: "DNSSearchListLargeCompliantRFC1035", buf: []byte{ 31, 33, 0, 0, 0, 0, 0, 0, 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 62, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 0, }, expectedErr: nil, }, { name: "DNSSearchListNonCompliantRFC1035", buf: []byte{ 31, 33, 0, 0, 0, 0, 0, 0, 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 0, 0, 0, 0, 0, 0, 0, 0, 0, }, expectedErr: ErrNDPOptMalformedBody, }, { name: "DNSSearchListValidSmall", buf: []byte{ 31, 2, 0, 0, 0, 0, 0, 0, 6, 'a', 'b', 'c', 'd', 'e', 'f', 0, }, expectedErr: nil, }, { name: "DNSSearchListTooSmall", buf: []byte{ 31, 1, 0, 0, 0, 0, 0, }, expectedErr: io.ErrUnexpectedEOF, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { opts := NDPOptions(test.buf) if _, err := opts.Iter(true); !errors.Is(err, test.expectedErr) { t.Fatalf("got Iter(true) = (_, %v), want = (_, %v)", err, test.expectedErr) } // test.buf may be malformed but we chose not to check // the iterator so it must return true. if _, err := opts.Iter(false); err != nil { t.Fatalf("got Iter(false) = (_, %s), want = (_, nil)", err) } }) } } // TestNDPOptionsIter tests that we can iterator over a valid NDPOptions. Note, // this test does not actually check any of the option's getters, it simply // checks the option Type and Body. We have other tests that tests the option // field gettings given an option body and don't need to duplicate those tests // here. func TestNDPOptionsIter(t *testing.T) { buf := []byte{ // Source Link-Layer Address. 1, 1, 1, 2, 3, 4, 5, 6, // Target Link-Layer Address. 2, 1, 7, 8, 9, 10, 11, 12, // 255 is an unrecognized type. If 255 ends up being the type // for some recognized type, update 255 to some other // unrecognized value. Note, this option should be skipped when // iterating. 255, 2, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, // Prefix information. 3, 4, 43, 64, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, } opts := NDPOptions(buf) it, err := opts.Iter(true) if err != nil { t.Fatalf("got Iter = (_, %s), want = (_, nil)", err) } // Test the first (Source Link-Layer) option. next, done, err := it.Next() if err != nil { t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err) } if done { t.Fatal("got Next = (_, true, _), want = (_, false, _)") } if got, want := []byte(next.(NDPSourceLinkLayerAddressOption)), buf[2:][:6]; !bytes.Equal(got, want) { t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want) } if got := next.kind(); got != ndpSourceLinkLayerAddressOptionType { t.Errorf("got Type = %d, want = %d", got, ndpSourceLinkLayerAddressOptionType) } // Test the next (Target Link-Layer) option. next, done, err = it.Next() if err != nil { t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err) } if done { t.Fatal("got Next = (_, true, _), want = (_, false, _)") } if got, want := []byte(next.(NDPTargetLinkLayerAddressOption)), buf[10:][:6]; !bytes.Equal(got, want) { t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want) } if got := next.kind(); got != ndpTargetLinkLayerAddressOptionType { t.Errorf("got Type = %d, want = %d", got, ndpTargetLinkLayerAddressOptionType) } // Test the next (Prefix Information) option. // Note, the unrecognized option should be skipped. next, done, err = it.Next() if err != nil { t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err) } if done { t.Fatal("got Next = (_, true, _), want = (_, false, _)") } if got, want := next.(NDPPrefixInformation), buf[34:][:30]; !bytes.Equal(got, want) { t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want) } if got := next.kind(); got != ndpPrefixInformationType { t.Errorf("got Type = %d, want = %d", got, ndpPrefixInformationType) } // Iterator should not return anything else. next, done, err = it.Next() if err != nil { t.Errorf("got Next = (_, _, %s), want = (_, _, nil)", err) } if !done { t.Error("got Next = (_, false, _), want = (_, true, _)") } if next != nil { t.Errorf("got Next = (%x, _, _), want = (nil, _, _)", next) } } func TestNDPRoutePreferenceStringer(t *testing.T) { p := NDPRoutePreference(0) for { var wantStr string switch p { case 0b01: wantStr = "HighRoutePreference" case 0b00: wantStr = "MediumRoutePreference" case 0b11: wantStr = "LowRoutePreference" case 0b10: wantStr = "ReservedRoutePreference" default: wantStr = fmt.Sprintf("NDPRoutePreference(%d)", p) } if gotStr := p.String(); gotStr != wantStr { t.Errorf("got NDPRoutePreference(%d).String() = %s, want = %s", p, gotStr, wantStr) } p++ if p == 0 { // Overflowed, we hit all values. break } } }