Support generating opaque interface identifiers as defined by RFC 7217

Support generating opaque interface identifiers as defined by RFC 7217 for
auto-generated IPv6 link-local addresses. Opaque interface identifiers will also
be used for IPv6 addresses auto-generated via SLAAC in a later change.

Note, this change does not handle retries in response to DAD conflicts yet.
That will also come in a later change.

Tests: Test that when configured to generated opaque IIDs, they are properly
generated as outlined by RFC 7217.
PiperOrigin-RevId: 288035349
This commit is contained in:
Ghanan Gowripalan
2020-01-03 13:00:19 -08:00
committed by gVisor bot
parent 1f384ac42b
commit d1d878a801
7 changed files with 384 additions and 15 deletions
+1
View File
@@ -44,6 +44,7 @@ go_test(
],
deps = [
":header",
"//pkg/rand",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"@com_github_google_go-cmp//cmp:go_default_library",
+45
View File
@@ -15,6 +15,7 @@
package header
import (
"crypto/sha256"
"encoding/binary"
"strings"
@@ -102,6 +103,11 @@ const (
// bytes including and after the IIDOffsetInIPv6Address-th byte are
// for the IID.
IIDOffsetInIPv6Address = 8
// OpaqueIIDSecretKeyMinBytes is the recommended minimum number of bytes
// for the secret key used to generate an opaque interface identifier as
// outlined by RFC 7217.
OpaqueIIDSecretKeyMinBytes = 16
)
// IPv6EmptySubnet is the empty IPv6 subnet. It may also be known as the
@@ -326,3 +332,42 @@ func IsV6LinkLocalAddress(addr tcpip.Address) bool {
}
return addr[0] == 0xfe && (addr[1]&0xc0) == 0x80
}
// AppendOpaqueInterfaceIdentifier appends a 64 bit opaque interface identifier
// (IID) to buf as outlined by RFC 7217 and returns the extended buffer.
//
// The opaque IID is generated from the cryptographic hash of the concatenation
// of the prefix, NIC's name, DAD counter (DAD retry counter) and the secret
// key. The secret key SHOULD be at least OpaqueIIDSecretKeyMinBytes bytes and
// MUST be generated to a pseudo-random number. See RFC 4086 for randomness
// requirements for security.
//
// If buf has enough capacity for the IID (IIDSize bytes), a new underlying
// array for the buffer will not be allocated.
func AppendOpaqueInterfaceIdentifier(buf []byte, prefix tcpip.Subnet, nicName string, dadCounter uint8, secretKey []byte) []byte {
// As per RFC 7217 section 5, the opaque identifier can be generated as a
// cryptographic hash of the concatenation of each of the function parameters.
// Note, we omit the optional Network_ID field.
h := sha256.New()
// h.Write never returns an error.
h.Write([]byte(prefix.ID()[:IIDOffsetInIPv6Address]))
h.Write([]byte(nicName))
h.Write([]byte{dadCounter})
h.Write(secretKey)
var sumBuf [sha256.Size]byte
sum := h.Sum(sumBuf[:0])
return append(buf, sum[:IIDSize]...)
}
// LinkLocalAddrWithOpaqueIID computes the default IPv6 link-local address with
// an opaque IID.
func LinkLocalAddrWithOpaqueIID(nicName string, dadCounter uint8, secretKey []byte) tcpip.Address {
lladdrb := [IPv6AddressSize]byte{
0: 0xFE,
1: 0x80,
}
return tcpip.Address(AppendOpaqueInterfaceIdentifier(lladdrb[:IIDOffsetInIPv6Address], IPv6LinkLocalPrefix.Subnet(), nicName, dadCounter, secretKey))
}
+163
View File
@@ -15,9 +15,12 @@
package header_test
import (
"bytes"
"crypto/sha256"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
@@ -43,3 +46,163 @@ func TestLinkLocalAddr(t *testing.T) {
t.Errorf("got LinkLocalAddr(%s) = %s, want = %s", linkAddr, got, want)
}
}
func TestAppendOpaqueInterfaceIdentifier(t *testing.T) {
var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes * 2]byte
if n, err := rand.Read(secretKeyBuf[:]); err != nil {
t.Fatalf("rand.Read(_): %s", err)
} else if want := header.OpaqueIIDSecretKeyMinBytes * 2; n != want {
t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", want, n)
}
tests := []struct {
name string
prefix tcpip.Subnet
nicName string
dadCounter uint8
secretKey []byte
}{
{
name: "SecretKey of minimum size",
prefix: header.IPv6LinkLocalPrefix.Subnet(),
nicName: "eth0",
dadCounter: 0,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes],
},
{
name: "SecretKey of less than minimum size",
prefix: func() tcpip.Subnet {
addrWithPrefix := tcpip.AddressWithPrefix{
Address: "\x01\x02\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
PrefixLen: header.IIDOffsetInIPv6Address * 8,
}
return addrWithPrefix.Subnet()
}(),
nicName: "eth10",
dadCounter: 1,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes/2],
},
{
name: "SecretKey of more than minimum size",
prefix: func() tcpip.Subnet {
addrWithPrefix := tcpip.AddressWithPrefix{
Address: "\x01\x02\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
PrefixLen: header.IIDOffsetInIPv6Address * 8,
}
return addrWithPrefix.Subnet()
}(),
nicName: "eth11",
dadCounter: 2,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes*2],
},
{
name: "Nil SecretKey",
prefix: func() tcpip.Subnet {
addrWithPrefix := tcpip.AddressWithPrefix{
Address: "\x01\x02\x03\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
PrefixLen: header.IIDOffsetInIPv6Address * 8,
}
return addrWithPrefix.Subnet()
}(),
nicName: "eth12",
dadCounter: 3,
secretKey: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
h := sha256.New()
h.Write([]byte(test.prefix.ID()[:header.IIDOffsetInIPv6Address]))
h.Write([]byte(test.nicName))
h.Write([]byte{test.dadCounter})
if k := test.secretKey; k != nil {
h.Write(k)
}
var hashSum [sha256.Size]byte
h.Sum(hashSum[:0])
want := hashSum[:header.IIDSize]
// Passing a nil buffer should result in a new buffer returned with the
// IID.
if got := header.AppendOpaqueInterfaceIdentifier(nil, test.prefix, test.nicName, test.dadCounter, test.secretKey); !bytes.Equal(got, want) {
t.Errorf("got AppendOpaqueInterfaceIdentifier(nil, %s, %s, %d, %x) = %x, want = %x", test.prefix, test.nicName, test.dadCounter, test.secretKey, got, want)
}
// Passing a buffer with sufficient capacity for the IID should populate
// the buffer provided.
var iidBuf [header.IIDSize]byte
if got := header.AppendOpaqueInterfaceIdentifier(iidBuf[:0], test.prefix, test.nicName, test.dadCounter, test.secretKey); !bytes.Equal(got, want) {
t.Errorf("got AppendOpaqueInterfaceIdentifier(iidBuf[:0], %s, %s, %d, %x) = %x, want = %x", test.prefix, test.nicName, test.dadCounter, test.secretKey, got, want)
}
if got := iidBuf[:]; !bytes.Equal(got, want) {
t.Errorf("got iidBuf = %x, want = %x", got, want)
}
})
}
}
func TestLinkLocalAddrWithOpaqueIID(t *testing.T) {
var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes * 2]byte
if n, err := rand.Read(secretKeyBuf[:]); err != nil {
t.Fatalf("rand.Read(_): %s", err)
} else if want := header.OpaqueIIDSecretKeyMinBytes * 2; n != want {
t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", want, n)
}
prefix := header.IPv6LinkLocalPrefix.Subnet()
tests := []struct {
name string
prefix tcpip.Subnet
nicName string
dadCounter uint8
secretKey []byte
}{
{
name: "SecretKey of minimum size",
nicName: "eth0",
dadCounter: 0,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes],
},
{
name: "SecretKey of less than minimum size",
nicName: "eth10",
dadCounter: 1,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes/2],
},
{
name: "SecretKey of more than minimum size",
nicName: "eth11",
dadCounter: 2,
secretKey: secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes*2],
},
{
name: "Nil SecretKey",
nicName: "eth12",
dadCounter: 3,
secretKey: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
addrBytes := [header.IPv6AddressSize]byte{
0: 0xFE,
1: 0x80,
}
want := tcpip.Address(header.AppendOpaqueInterfaceIdentifier(
addrBytes[:header.IIDOffsetInIPv6Address],
prefix,
test.nicName,
test.dadCounter,
test.secretKey,
))
if got := header.LinkLocalAddrWithOpaqueIID(test.nicName, test.dadCounter, test.secretKey); got != want {
t.Errorf("got LinkLocalAddrWithOpaqueIID(%s, %d, %x) = %s, want = %s", test.nicName, test.dadCounter, test.secretKey, got, want)
}
})
}
}
+1
View File
@@ -59,6 +59,7 @@ go_test(
],
deps = [
":stack",
"//pkg/rand",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/checker",
+15 -11
View File
@@ -178,20 +178,24 @@ func (n *NIC) enable() *tcpip.Error {
return nil
}
l2addr := n.linkEP.LinkAddress()
var addr tcpip.Address
if oIID := n.stack.opaqueIIDOpts; oIID.NICNameFromID != nil {
addr = header.LinkLocalAddrWithOpaqueIID(oIID.NICNameFromID(n.ID()), 0, oIID.SecretKey)
} else {
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
// 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)
}
addr := header.LinkLocalAddr(l2addr)
_, err := n.addPermanentAddressLocked(tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
+36
View File
@@ -352,6 +352,33 @@ func (u *uniqueIDGenerator) UniqueID() uint64 {
return atomic.AddUint64((*uint64)(u), 1)
}
// NICNameFromID is a function that returns a stable name for the specified NIC,
// even if the NIC ID changes over time.
type NICNameFromID func(tcpip.NICID) string
// OpaqueInterfaceIdentifierOptions holds the options related to the generation
// of opaque interface indentifiers (IIDs) as defined by RFC 7217.
type OpaqueInterfaceIdentifierOptions struct {
// NICNameFromID is a function that returns a stable name for a specified NIC,
// even if the NIC ID changes over time.
//
// Must be specified to generate the opaque IID.
NICNameFromID NICNameFromID
// SecretKey is a pseudo-random number used as the secret key when generating
// opaque IIDs as defined by RFC 7217. The key SHOULD be at least
// header.OpaqueIIDSecretKeyMinBytes bytes and MUST follow minimum randomness
// requirements for security as outlined by RFC 4086. SecretKey MUST NOT
// change between program runs, unless explicitly changed.
//
// OpaqueInterfaceIdentifierOptions takes ownership of SecretKey. SecretKey
// MUST NOT be modified after Stack is created.
//
// May be nil, but a nil value is highly discouraged to maintain
// some level of randomness between nodes.
SecretKey []byte
}
// Stack is a networking stack, with all supported protocols, NICs, and route
// table.
type Stack struct {
@@ -422,6 +449,10 @@ type Stack struct {
// uniqueIDGenerator is a generator of unique identifiers.
uniqueIDGenerator UniqueID
// opaqueIIDOpts hold the options for generating opaque interface identifiers
// (IIDs) as outlined by RFC 7217.
opaqueIIDOpts OpaqueInterfaceIdentifierOptions
}
// UniqueID is an abstract generator of unique identifiers.
@@ -479,6 +510,10 @@ type Options struct {
// RawFactory produces raw endpoints. Raw endpoints are enabled only if
// this is non-nil.
RawFactory RawFactory
// OpaqueIIDOpts hold the options for generating opaque interface identifiers
// (IIDs) as outlined by RFC 7217.
OpaqueIIDOpts OpaqueInterfaceIdentifierOptions
}
// TransportEndpointInfo holds useful information about a transport endpoint
@@ -549,6 +584,7 @@ func New(opts Options) *Stack {
autoGenIPv6LinkLocal: opts.AutoGenIPv6LinkLocal,
uniqueIDGenerator: opts.UniqueID,
ndpDisp: opts.NDPDisp,
opaqueIIDOpts: opts.OpaqueIIDOpts,
}
// Add specified network protocols.
+123 -4
View File
@@ -27,6 +27,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
@@ -1894,55 +1895,67 @@ func TestNICForwarding(t *testing.T) {
}
// 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.
// using the modified EUI-64 of the NIC's MAC address (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
iidOpts stack.OpaqueInterfaceIdentifierOptions
shouldGen bool
}{
{
"Disabled",
false,
linkAddr1,
stack.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(nicID tcpip.NICID) string {
return fmt.Sprintf("nic%d", nicID)
},
},
false,
},
{
"Enabled",
true,
linkAddr1,
stack.OpaqueInterfaceIdentifierOptions{},
true,
},
{
"Nil MAC",
true,
tcpip.LinkAddress([]byte(nil)),
stack.OpaqueInterfaceIdentifierOptions{},
false,
},
{
"Empty MAC",
true,
tcpip.LinkAddress(""),
stack.OpaqueInterfaceIdentifierOptions{},
false,
},
{
"Invalid MAC",
true,
tcpip.LinkAddress("\x01\x02\x03"),
stack.OpaqueInterfaceIdentifierOptions{},
false,
},
{
"Multicast MAC",
true,
tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
stack.OpaqueInterfaceIdentifierOptions{},
false,
},
{
"Unspecified MAC",
true,
tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"),
stack.OpaqueInterfaceIdentifierOptions{},
false,
},
}
@@ -1951,6 +1964,112 @@ func TestNICAutoGenAddr(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
OpaqueIIDOpts: test.iidOpts,
}
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)
}
}
})
}
}
// TestNICAutoGenAddrWithOpaque tests the auto-generation of IPv6 link-local
// addresses with opaque interface identifiers. Link Local addresses should
// always be generated with opaque IIDs if configured to use them, even if the
// NIC has an invalid MAC address.
func TestNICAutoGenAddrWithOpaque(t *testing.T) {
var secretKey [header.OpaqueIIDSecretKeyMinBytes]byte
n, err := rand.Read(secretKey[:])
if err != nil {
t.Fatalf("rand.Read(_): %s", err)
}
if n != header.OpaqueIIDSecretKeyMinBytes {
t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", header.OpaqueIIDSecretKeyMinBytes, n)
}
iidOpts := stack.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(nicID tcpip.NICID) string {
return fmt.Sprintf("nic%d", nicID)
},
SecretKey: secretKey[:],
}
tests := []struct {
name string
autoGen bool
linkAddr tcpip.LinkAddress
}{
{
"Disabled",
false,
linkAddr1,
},
{
"Enabled",
true,
linkAddr1,
},
// These are all cases where we would not have generated a
// link-local address if opaque IIDs were disabled.
{
"Nil MAC",
true,
tcpip.LinkAddress([]byte(nil)),
},
{
"Empty MAC",
true,
tcpip.LinkAddress(""),
},
{
"Invalid MAC",
true,
tcpip.LinkAddress("\x01\x02\x03"),
},
{
"Multicast MAC",
true,
tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
},
{
"Unspecified MAC",
true,
tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
OpaqueIIDOpts: iidOpts,
}
if test.autoGen {
@@ -1972,10 +2091,10 @@ func TestNICAutoGenAddr(t *testing.T) {
t.Fatalf("stack.GetMainNICAddress(_, _) err = %s", err)
}
if test.shouldGen {
if test.autoGen {
// 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 {
if want := (tcpip.AddressWithPrefix{Address: header.LinkLocalAddrWithOpaqueIID("nic1", 0, secretKey[:]), PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = %s, want = %s", addr, want)
}
} else {