Auto-generate an IPv6 link-local address based on the NIC's MAC Address.

This change adds support for optionally auto-generating an IPv6 link-local
address based on the NIC's MAC Address on NIC enable.

Note, this change will not break existing uses of netstack as the default
configuration for the stack options is set in such a way that a link-local
address will not be auto-generated unless the stack is explicitly configured.
See `stack.Options` for more details. Specifically, see
`stack.Options.AutoGenIPv6LinkLocal`.

Tests: Tests to make sure that the IPb6 link-local address is only
auto-generated if the stack is specifically configured to do so. Also tests to
make sure that an auto-generated address goes through the DAD process.
PiperOrigin-RevId: 276059813
This commit is contained in:
Ghanan Gowripalan 2019-10-22 07:25:35 -07:00 committed by gVisor bot
parent 070a8c2d4c
commit fb69de696b
8 changed files with 350 additions and 18 deletions

View File

@ -45,7 +45,10 @@ go_test(
go_test(
name = "header_test",
size = "small",
srcs = ["ndp_test.go"],
srcs = [
"eth_test.go",
"ndp_test.go",
],
embed = [":header"],
deps = ["//pkg/tcpip"],
)

View File

@ -48,6 +48,28 @@ const (
// EthernetAddressSize is the size, in bytes, of an ethernet address.
EthernetAddressSize = 6
// unspecifiedEthernetAddress is the unspecified ethernet address
// (all bits set to 0).
unspecifiedEthernetAddress = tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00")
// unicastMulticastFlagMask is the mask of the least significant bit in
// the first octet (in network byte order) of an ethernet address that
// determines whether the ethernet address is a unicast or multicast. If
// the masked bit is a 1, then the address is a multicast, unicast
// otherwise.
//
// See the IEEE Std 802-2001 document for more details. Specifically,
// section 9.2.1 of http://ieee802.org/secmail/pdfocSP2xXA6d.pdf:
// "A 48-bit universal address consists of two parts. The first 24 bits
// correspond to the OUI as assigned by the IEEE, expect that the
// assignee may set the LSB of the first octet to 1 for group addresses
// or set it to 0 for individual addresses."
unicastMulticastFlagMask = 1
// unicastMulticastFlagByteIdx is the byte that holds the
// unicast/multicast flag. See unicastMulticastFlagMask.
unicastMulticastFlagByteIdx = 0
)
const (
@ -90,3 +112,25 @@ func (b Ethernet) Encode(e *EthernetFields) {
copy(b[srcMAC:][:EthernetAddressSize], e.SrcAddr)
copy(b[dstMAC:][:EthernetAddressSize], e.DstAddr)
}
// IsValidUnicastEthernetAddress returns true if addr is a valid unicast
// ethernet address.
func IsValidUnicastEthernetAddress(addr tcpip.LinkAddress) bool {
// Must be of the right length.
if len(addr) != EthernetAddressSize {
return false
}
// Must not be unspecified.
if addr == unspecifiedEthernetAddress {
return false
}
// Must not be a multicast.
if addr[unicastMulticastFlagByteIdx]&unicastMulticastFlagMask != 0 {
return false
}
// addr is a valid unicast ethernet address.
return true
}

View File

@ -0,0 +1,68 @@
// Copyright 2018 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 (
"testing"
"gvisor.dev/gvisor/pkg/tcpip"
)
func TestIsValidUnicastEthernetAddress(t *testing.T) {
tests := []struct {
name string
addr tcpip.LinkAddress
expected bool
}{
{
"Nil",
tcpip.LinkAddress([]byte(nil)),
false,
},
{
"Empty",
tcpip.LinkAddress(""),
false,
},
{
"InvalidLength",
tcpip.LinkAddress("\x01\x02\x03"),
false,
},
{
"Unspecified",
unspecifiedEthernetAddress,
false,
},
{
"Multicast",
tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
false,
},
{
"Valid",
tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06"),
true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := IsValidUnicastEthernetAddress(test.addr); got != test.expected {
t.Fatalf("got IsValidUnicastEthernetAddress = %t, want = %t", got, test.expected)
}
})
}
}

View File

@ -101,6 +101,15 @@ var IPv6EmptySubnet = func() tcpip.Subnet {
return subnet
}()
// IPv6LinkLocalPrefix is the prefix for IPv6 link-local addresses, as defined
// by RFC 4291 section 2.5.6.
//
// The prefix is fe80::/64
var IPv6LinkLocalPrefix = tcpip.AddressWithPrefix{
Address: "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
PrefixLen: 64,
}
// PayloadLength returns the value of the "payload length" field of the ipv6
// header.
func (b IPv6) PayloadLength() uint16 {

View File

@ -31,8 +31,7 @@ import (
const (
addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
linkAddr1 = "\x01\x02\x03\x04\x05\x06"
linkAddr2 = "\x01\x02\x03\x04\x05\x07"
linkAddr1 = "\x02\x02\x03\x04\x05\x06"
)
// TestDADDisabled tests that an address successfully resolves immediately

View File

@ -81,6 +81,9 @@ const (
)
func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, loopback bool) *NIC {
// TODO(b/141011931): Validate a LinkEndpoint (ep) is valid. For
// example, make sure that the link address it provides is a valid
// unicast ethernet address.
nic := &NIC{
stack: stack,
id: id,
@ -139,11 +142,50 @@ func (n *NIC) enable() *tcpip.Error {
// when we perform Duplicate Address Detection, or Router Advertisement
// when we do Router Discovery. See RFC 4862, section 5.4.2 and RFC 4861
// section 4.2 for more information.
if _, ok := n.stack.networkProtocols[header.IPv6ProtocolNumber]; ok {
return n.joinGroup(header.IPv6ProtocolNumber, header.IPv6AllNodesMulticastAddress)
//
// Also auto-generate an IPv6 link-local address based on the NIC's
// link address if it is configured to do so. Note, each interface is
// required to have IPv6 link-local unicast address, as per RFC 4291
// section 2.1.
_, ok := n.stack.networkProtocols[header.IPv6ProtocolNumber]
if !ok {
return nil
}
return nil
n.mu.Lock()
defer n.mu.Unlock()
if err := n.joinGroupLocked(header.IPv6ProtocolNumber, header.IPv6AllNodesMulticastAddress); err != nil {
return err
}
if !n.stack.autoGenIPv6LinkLocal {
return nil
}
l2addr := n.linkEP.LinkAddress()
// Only attempt to generate the link-local address if we have a
// valid MAC address.
//
// TODO(b/141011931): Validate a LinkEndpoint's link address
// (provided by LinkEndpoint.LinkAddress) before reaching this
// point.
if !header.IsValidUnicastEthernetAddress(l2addr) {
return nil
}
addr := header.LinkLocalAddr(l2addr)
_, err := n.addPermanentAddressLocked(tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen,
},
}, CanBePrimaryEndpoint)
return err
}
// attachLinkEndpoint attaches the NIC to the endpoint, which will enable it
@ -582,6 +624,11 @@ func (n *NIC) joinGroup(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address
// exists yet. Otherwise it just increments its count. n MUST be locked before
// joinGroupLocked is called.
func (n *NIC) joinGroupLocked(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address) *tcpip.Error {
// TODO(b/143102137): When implementing MLD, make sure MLD packets are
// not sent unless a valid link-local address is available for use on n
// as an MLD packet's source address must be a link-local address as
// outlined in RFC 3810 section 5.
id := NetworkEndpointID{addr}
joins := n.mcastJoins[id]
if joins == 0 {

View File

@ -401,6 +401,11 @@ type Stack struct {
// ndpConfigs is the NDP configurations used by interfaces.
ndpConfigs NDPConfigurations
// autoGenIPv6LinkLocal determines whether or not the stack will attempt
// to auto-generate an IPv6 link-local address for newly enabled NICs.
// See the AutoGenIPv6LinkLocal field of Options for more details.
autoGenIPv6LinkLocal bool
}
// Options contains optional Stack configuration.
@ -431,6 +436,18 @@ type Options struct {
// before assigning an address to a NIC.
NDPConfigs NDPConfigurations
// AutoGenIPv6LinkLocal determins whether or not the stack will attempt
// to auto-generate an IPv6 link-local address for newly enabled NICs.
// Note, setting this to true does not mean that a link-local address
// will be assigned right away, or at all. If Duplicate Address
// Detection is enabled, an address will only be assigned if it
// successfully resolves. If it fails, no further attempt will be made
// to auto-generate an IPv6 link-local address.
//
// The generated link-local address will follow RFC 4291 Appendix A
// guidelines.
AutoGenIPv6LinkLocal bool
// RawFactory produces raw endpoints. Raw endpoints are enabled only if
// this is non-nil.
RawFactory RawFactory
@ -484,18 +501,19 @@ func New(opts Options) *Stack {
opts.NDPConfigs.validate()
s := &Stack{
transportProtocols: make(map[tcpip.TransportProtocolNumber]*transportProtocolState),
networkProtocols: make(map[tcpip.NetworkProtocolNumber]NetworkProtocol),
linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]LinkAddressResolver),
nics: make(map[tcpip.NICID]*NIC),
linkAddrCache: newLinkAddrCache(ageLimit, resolutionTimeout, resolutionAttempts),
PortManager: ports.NewPortManager(),
clock: clock,
stats: opts.Stats.FillIn(),
handleLocal: opts.HandleLocal,
icmpRateLimiter: NewICMPRateLimiter(),
portSeed: generateRandUint32(),
ndpConfigs: opts.NDPConfigs,
transportProtocols: make(map[tcpip.TransportProtocolNumber]*transportProtocolState),
networkProtocols: make(map[tcpip.NetworkProtocolNumber]NetworkProtocol),
linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]LinkAddressResolver),
nics: make(map[tcpip.NICID]*NIC),
linkAddrCache: newLinkAddrCache(ageLimit, resolutionTimeout, resolutionAttempts),
PortManager: ports.NewPortManager(),
clock: clock,
stats: opts.Stats.FillIn(),
handleLocal: opts.HandleLocal,
icmpRateLimiter: NewICMPRateLimiter(),
portSeed: generateRandUint32(),
ndpConfigs: opts.NDPConfigs,
autoGenIPv6LinkLocal: opts.AutoGenIPv6LinkLocal,
}
// Add specified network protocols.

View File

@ -24,11 +24,13 @@ import (
"sort"
"strings"
"testing"
"time"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@ -1864,3 +1866,145 @@ func TestNICForwarding(t *testing.T) {
t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
}
}
// TestNICAutoGenAddr tests the auto-generation of IPv6 link-local addresses
// (or lack there-of if disabled (default)). Note, DAD will be disabled in
// these tests.
func TestNICAutoGenAddr(t *testing.T) {
tests := []struct {
name string
autoGen bool
linkAddr tcpip.LinkAddress
shouldGen bool
}{
{
"Disabled",
false,
linkAddr1,
false,
},
{
"Enabled",
true,
linkAddr1,
true,
},
{
"Nil MAC",
true,
tcpip.LinkAddress([]byte(nil)),
false,
},
{
"Empty MAC",
true,
tcpip.LinkAddress(""),
false,
},
{
"Invalid MAC",
true,
tcpip.LinkAddress("\x01\x02\x03"),
false,
},
{
"Multicast MAC",
true,
tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
false,
},
{
"Unspecified MAC",
true,
tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"),
false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
}
if test.autoGen {
// Only set opts.AutoGenIPv6LinkLocal when
// test.autoGen is true because
// opts.AutoGenIPv6LinkLocal should be false by
// default.
opts.AutoGenIPv6LinkLocal = true
}
e := channel.New(10, 1280, test.linkAddr)
s := stack.New(opts)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(_) = %s", err)
}
addr, err := s.GetMainNICAddress(1, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("stack.GetMainNICAddress(_, _) err = %s", err)
}
if test.shouldGen {
// Should have auto-generated an address and
// resolved immediately (DAD is disabled).
if want := (tcpip.AddressWithPrefix{Address: header.LinkLocalAddr(test.linkAddr), PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = %s, want = %s", addr, want)
}
} else {
// Should not have auto-generated an address.
if want := (tcpip.AddressWithPrefix{}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (%s, nil), want = (%s, nil)", addr, want)
}
}
})
}
}
// TestNICAutoGenAddrDoesDAD tests that the successful auto-generation of IPv6
// link-local addresses will only be assigned after the DAD process resolves.
func TestNICAutoGenAddrDoesDAD(t *testing.T) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NDPConfigs: stack.NDPConfigurations{
RetransmitTimer: time.Second,
DupAddrDetectTransmits: 1,
},
AutoGenIPv6LinkLocal: true,
}
e := channel.New(10, 1280, linkAddr1)
s := stack.New(opts)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(_) = %s", err)
}
// Address should not be considered bound to the
// NIC yet (DAD ongoing).
addr, err := s.GetMainNICAddress(1, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (_, %v), want = (_, nil)", err)
}
if want := (tcpip.AddressWithPrefix{}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (%s, nil), want = (%s, nil)", addr, want)
}
// Wait for the address to resolve (an extra
// 250ms to make sure the address resolves).
//
// TODO(b/140896005): Use events from the
// netstack to know immediately when DAD
// completes.
time.Sleep(time.Second + 250*time.Millisecond)
// Should have auto-generated an address and
// resolved (if DAD).
addr, err = s.GetMainNICAddress(1, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("stack.GetMainNICAddress(_, _) err = %s", err)
}
if want := (tcpip.AddressWithPrefix{Address: header.LinkLocalAddr(linkAddr1), PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = %s, want = %s", addr, want)
}
}