summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGhanan Gowripalan <ghanan@google.com>2020-01-28 13:37:10 -0800
committergVisor bot <gvisor-bot@google.com>2020-01-28 13:52:04 -0800
commitce0bac4be9d808877248c328fac07ff0d66b9607 (patch)
tree40c627ad4cf71e5094b451946920d5c168fce3af
parentf263801a74d4ccac042b068d0928c8738e40af5b (diff)
Include the NDP Source Link Layer option when sending DAD messages
Test: stack_test.TestDADResolve PiperOrigin-RevId: 292003124
-rw-r--r--pkg/tcpip/checker/checker.go50
-rw-r--r--pkg/tcpip/header/icmpv6.go2
-rw-r--r--pkg/tcpip/header/ndp_options.go35
-rw-r--r--pkg/tcpip/stack/ndp.go22
-rw-r--r--pkg/tcpip/stack/ndp_test.go6
5 files changed, 108 insertions, 7 deletions
diff --git a/pkg/tcpip/checker/checker.go b/pkg/tcpip/checker/checker.go
index 885d773b0..4d6ae0871 100644
--- a/pkg/tcpip/checker/checker.go
+++ b/pkg/tcpip/checker/checker.go
@@ -771,6 +771,56 @@ func NDPNSTargetAddress(want tcpip.Address) TransportChecker {
}
}
+// NDPNSOptions creates a checker that checks that the packet contains the
+// provided NDP options within an NDP Neighbor Solicitation message.
+//
+// The returned TransportChecker assumes that a valid ICMPv6 is passed to it
+// containing a valid NDPNS message as far as the size is concerned.
+func NDPNSOptions(opts []header.NDPOption) TransportChecker {
+ return func(t *testing.T, h header.Transport) {
+ t.Helper()
+
+ icmp := h.(header.ICMPv6)
+ ns := header.NDPNeighborSolicit(icmp.NDPPayload())
+ it, err := ns.Options().Iter(true)
+ if err != nil {
+ t.Errorf("opts.Iter(true): %s", err)
+ return
+ }
+
+ i := 0
+ for {
+ opt, done, _ := it.Next()
+ if done {
+ break
+ }
+
+ if i >= len(opts) {
+ t.Errorf("got unexpected option: %s", opt)
+ continue
+ }
+
+ switch wantOpt := opts[i].(type) {
+ case header.NDPSourceLinkLayerAddressOption:
+ gotOpt, ok := opt.(header.NDPSourceLinkLayerAddressOption)
+ if !ok {
+ t.Errorf("got type = %T at index = %d; want = %T", opt, i, wantOpt)
+ } else if got, want := gotOpt.EthernetAddress(), wantOpt.EthernetAddress(); got != want {
+ t.Errorf("got EthernetAddress() = %s at index %d, want = %s", got, i, want)
+ }
+ default:
+ panic("not implemented")
+ }
+
+ i++
+ }
+
+ if missing := opts[i:]; len(missing) > 0 {
+ t.Errorf("missing options: %s", missing)
+ }
+ }
+}
+
// NDPRS creates a checker that checks that the packet contains a valid NDP
// Router Solicitation message (as per the raw wire format).
func NDPRS() NetworkChecker {
diff --git a/pkg/tcpip/header/icmpv6.go b/pkg/tcpip/header/icmpv6.go
index b095dc0ab..c7ee2de57 100644
--- a/pkg/tcpip/header/icmpv6.go
+++ b/pkg/tcpip/header/icmpv6.go
@@ -52,7 +52,7 @@ const (
// ICMPv6NeighborAdvertSize is size of a neighbor advertisement
// including the NDP Target Link Layer option for an Ethernet
// address.
- ICMPv6NeighborAdvertSize = ICMPv6HeaderSize + NDPNAMinimumSize + ndpLinkLayerAddressSize
+ ICMPv6NeighborAdvertSize = ICMPv6HeaderSize + NDPNAMinimumSize + NDPLinkLayerAddressSize
// ICMPv6EchoMinimumSize is the minimum size of a valid ICMP echo packet.
ICMPv6EchoMinimumSize = 8
diff --git a/pkg/tcpip/header/ndp_options.go b/pkg/tcpip/header/ndp_options.go
index 1e60f3d4f..e6a6ad39b 100644
--- a/pkg/tcpip/header/ndp_options.go
+++ b/pkg/tcpip/header/ndp_options.go
@@ -17,6 +17,7 @@ package header
import (
"encoding/binary"
"errors"
+ "fmt"
"math"
"time"
@@ -32,9 +33,9 @@ const (
// Address option, as per RFC 4861 section 4.6.1.
NDPTargetLinkLayerAddressOptionType = 2
- // ndpLinkLayerAddressSize is the size of a Source or Target Link Layer
- // Address option.
- ndpLinkLayerAddressSize = 8
+ // NDPLinkLayerAddressSize is the size of a Source or Target Link Layer
+ // Address option for an Ethernet address.
+ NDPLinkLayerAddressSize = 8
// NDPPrefixInformationType is the type of the Prefix Information
// option, as per RFC 4861 section 4.6.2.
@@ -300,6 +301,8 @@ func (b NDPOptions) Serialize(s NDPOptionsSerializer) int {
// NDPOption is the set of functions to be implemented by all NDP option types.
type NDPOption interface {
+ fmt.Stringer
+
// Type returns the type of the receiver.
Type() uint8
@@ -397,6 +400,11 @@ func (o NDPSourceLinkLayerAddressOption) serializeInto(b []byte) int {
return copy(b, o)
}
+// String implements fmt.Stringer.String.
+func (o NDPSourceLinkLayerAddressOption) String() string {
+ return fmt.Sprintf("%T(%s)", o, tcpip.LinkAddress(o))
+}
+
// EthernetAddress will return an ethernet (MAC) address if the
// NDPSourceLinkLayerAddressOption's body has at minimum EthernetAddressSize
// bytes. If the body has more than EthernetAddressSize bytes, only the first
@@ -432,6 +440,11 @@ func (o NDPTargetLinkLayerAddressOption) serializeInto(b []byte) int {
return copy(b, o)
}
+// String implements fmt.Stringer.String.
+func (o NDPTargetLinkLayerAddressOption) String() string {
+ return fmt.Sprintf("%T(%s)", o, tcpip.LinkAddress(o))
+}
+
// EthernetAddress will return an ethernet (MAC) address if the
// NDPTargetLinkLayerAddressOption's body has at minimum EthernetAddressSize
// bytes. If the body has more than EthernetAddressSize bytes, only the first
@@ -478,6 +491,17 @@ func (o NDPPrefixInformation) serializeInto(b []byte) int {
return used
}
+// String implements fmt.Stringer.String.
+func (o NDPPrefixInformation) String() string {
+ return fmt.Sprintf("%T(O=%t, A=%t, PL=%s, VL=%s, Prefix=%s)",
+ o,
+ o.OnLinkFlag(),
+ o.AutonomousAddressConfigurationFlag(),
+ o.PreferredLifetime(),
+ o.ValidLifetime(),
+ o.Subnet())
+}
+
// PrefixLength returns the value in the number of leading bits in the Prefix
// that are valid.
//
@@ -587,6 +611,11 @@ func (o NDPRecursiveDNSServer) serializeInto(b []byte) int {
return used
}
+// String implements fmt.Stringer.String.
+func (o NDPRecursiveDNSServer) String() string {
+ return fmt.Sprintf("%T(%s valid for %s)", o, o.Addresses(), o.Lifetime())
+}
+
// Lifetime returns the length of time that the DNS server addresses
// in this option may be used for name resolution.
//
diff --git a/pkg/tcpip/stack/ndp.go b/pkg/tcpip/stack/ndp.go
index d983ac390..245694118 100644
--- a/pkg/tcpip/stack/ndp.go
+++ b/pkg/tcpip/stack/ndp.go
@@ -538,11 +538,29 @@ func (ndp *ndpState) sendDADPacket(addr tcpip.Address) *tcpip.Error {
r := makeRoute(header.IPv6ProtocolNumber, header.IPv6Any, snmc, ndp.nic.linkEP.LinkAddress(), ref, false, false)
defer r.Release()
- hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.ICMPv6NeighborSolicitMinimumSize)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize))
+ linkAddr := ndp.nic.linkEP.LinkAddress()
+ isValidLinkAddr := header.IsValidUnicastEthernetAddress(linkAddr)
+ ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize
+ if isValidLinkAddr {
+ // Only include a Source Link Layer Address option if the NIC has a valid
+ // link layer address.
+ //
+ // TODO(b/141011931): Validate a LinkEndpoint's link address (provided by
+ // LinkEndpoint.LinkAddress) before reaching this point.
+ ndpNSSize += header.NDPLinkLayerAddressSize
+ }
+
+ hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + ndpNSSize)
+ pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
pkt.SetType(header.ICMPv6NeighborSolicit)
ns := header.NDPNeighborSolicit(pkt.NDPPayload())
ns.SetTargetAddress(addr)
+
+ if isValidLinkAddr {
+ ns.Options().Serialize(header.NDPOptionsSerializer{
+ header.NDPSourceLinkLayerAddressOption(linkAddr),
+ })
+ }
pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
sent := r.Stats().ICMP.V6PacketsSent
diff --git a/pkg/tcpip/stack/ndp_test.go b/pkg/tcpip/stack/ndp_test.go
index ad2c6f601..726468e41 100644
--- a/pkg/tcpip/stack/ndp_test.go
+++ b/pkg/tcpip/stack/ndp_test.go
@@ -417,7 +417,11 @@ func TestDADResolve(t *testing.T) {
checker.IPv6(t, p.Pkt.Header.View().ToVectorisedView().First(),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
- checker.NDPNSTargetAddress(addr1)))
+ checker.NDPNSTargetAddress(addr1),
+ checker.NDPNSOptions([]header.NDPOption{
+ header.NDPSourceLinkLayerAddressOption(linkAddr1),
+ }),
+ ))
}
})
}