Do Source Address Selection when choosing an IPv6 source address

Do Source Address Selection when choosing an IPv6 source address as per RFC 6724
section 5 rules 1-3:
1) Prefer same address
2) Prefer appropriate scope
3) Avoid deprecated addresses.

A later change will update Source Address Selection to follow rules 4-8.

Tests:
Rule 1 & 2: stack.TestIPv6SourceAddressSelectionScopeAndSameAddress,
Rule 3:     stack.TestAutoGenAddrTimerDeprecation,
            stack.TestAutoGenAddrDeprecateFromPI
PiperOrigin-RevId: 289559373
This commit is contained in:
Ghanan Gowripalan
2020-01-13 17:58:28 -08:00
committed by gVisor bot
parent debd213da6
commit 1ad8381eac
6 changed files with 420 additions and 16 deletions
+43
View File
@@ -333,6 +333,17 @@ func IsV6LinkLocalAddress(addr tcpip.Address) bool {
return addr[0] == 0xfe && (addr[1]&0xc0) == 0x80
}
// IsV6UniqueLocalAddress determines if the provided address is an IPv6
// unique-local address (within the prefix FC00::/7).
func IsV6UniqueLocalAddress(addr tcpip.Address) bool {
if len(addr) != IPv6AddressSize {
return false
}
// According to RFC 4193 section 3.1, a unique local address has the prefix
// FC00::/7.
return (addr[0] & 0xfe) == 0xfc
}
// AppendOpaqueInterfaceIdentifier appends a 64 bit opaque interface identifier
// (IID) to buf as outlined by RFC 7217 and returns the extended buffer.
//
@@ -371,3 +382,35 @@ func LinkLocalAddrWithOpaqueIID(nicName string, dadCounter uint8, secretKey []by
return tcpip.Address(AppendOpaqueInterfaceIdentifier(lladdrb[:IIDOffsetInIPv6Address], IPv6LinkLocalPrefix.Subnet(), nicName, dadCounter, secretKey))
}
// IPv6AddressScope is the scope of an IPv6 address.
type IPv6AddressScope int
const (
// LinkLocalScope indicates a link-local address.
LinkLocalScope IPv6AddressScope = iota
// UniqueLocalScope indicates a unique-local address.
UniqueLocalScope
// GlobalScope indicates a global address.
GlobalScope
)
// ScopeForIPv6Address returns the scope for an IPv6 address.
func ScopeForIPv6Address(addr tcpip.Address) (IPv6AddressScope, *tcpip.Error) {
if len(addr) != IPv6AddressSize {
return GlobalScope, tcpip.ErrBadAddress
}
switch {
case IsV6LinkLocalAddress(addr):
return LinkLocalScope, nil
case IsV6UniqueLocalAddress(addr):
return UniqueLocalScope, nil
default:
return GlobalScope, nil
}
}
+95 -1
View File
@@ -25,7 +25,13 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header"
)
const linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
const (
linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
linkLocalAddr = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
uniqueLocalAddr1 = tcpip.Address("\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
uniqueLocalAddr2 = tcpip.Address("\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
globalAddr = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
)
func TestEthernetAdddressToModifiedEUI64(t *testing.T) {
expectedIID := [header.IIDSize]byte{0, 2, 3, 255, 254, 4, 5, 6}
@@ -206,3 +212,91 @@ func TestLinkLocalAddrWithOpaqueIID(t *testing.T) {
})
}
}
func TestIsV6UniqueLocalAddress(t *testing.T) {
tests := []struct {
name string
addr tcpip.Address
expected bool
}{
{
name: "Valid Unique 1",
addr: uniqueLocalAddr1,
expected: true,
},
{
name: "Valid Unique 2",
addr: uniqueLocalAddr1,
expected: true,
},
{
name: "Link Local",
addr: linkLocalAddr,
expected: false,
},
{
name: "Global",
addr: globalAddr,
expected: false,
},
{
name: "IPv4",
addr: "\x01\x02\x03\x04",
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := header.IsV6UniqueLocalAddress(test.addr); got != test.expected {
t.Errorf("got header.IsV6UniqueLocalAddress(%s) = %t, want = %t", test.addr, got, test.expected)
}
})
}
}
func TestScopeForIPv6Address(t *testing.T) {
tests := []struct {
name string
addr tcpip.Address
scope header.IPv6AddressScope
err *tcpip.Error
}{
{
name: "Unique Local",
addr: uniqueLocalAddr1,
scope: header.UniqueLocalScope,
err: nil,
},
{
name: "Link Local",
addr: linkLocalAddr,
scope: header.LinkLocalScope,
err: nil,
},
{
name: "Global",
addr: globalAddr,
scope: header.GlobalScope,
err: nil,
},
{
name: "IPv4",
addr: "\x01\x02\x03\x04",
scope: header.GlobalScope,
err: tcpip.ErrBadAddress,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := header.ScopeForIPv6Address(test.addr)
if err != test.err {
t.Errorf("got header.IsV6UniqueLocalAddress(%s) = (_, %v), want = (_, %v)", test.addr, err, test.err)
}
if got != test.scope {
t.Errorf("got header.IsV6UniqueLocalAddress(%s) = (%d, _), want = (%d, _)", test.addr, got, test.scope)
}
})
}
}
+17 -5
View File
@@ -1732,9 +1732,11 @@ func stackAndNdpDispatcherWithDefaultRoute(t *testing.T, nicID tcpip.NICID) (*nd
return ndpDisp, e, s
}
// addrForNewConnection returns the local address used when creating a new
// connection.
func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
// addrForNewConnectionTo returns the local address used when creating a new
// connection to addr.
func addrForNewConnectionTo(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
t.Helper()
wq := waiter.Queue{}
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.EventIn)
@@ -1748,8 +1750,8 @@ func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
if err := ep.SetSockOptBool(tcpip.V6OnlyOption, true); err != nil {
t.Fatalf("SetSockOpt(tcpip.V6OnlyOption, true): %s", err)
}
if err := ep.Connect(dstAddr); err != nil {
t.Fatalf("ep.Connect(%+v): %s", dstAddr, err)
if err := ep.Connect(addr); err != nil {
t.Fatalf("ep.Connect(%+v): %s", addr, err)
}
got, err := ep.GetLocalAddress()
if err != nil {
@@ -1758,9 +1760,19 @@ func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
return got.Addr
}
// addrForNewConnection returns the local address used when creating a new
// connection.
func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
t.Helper()
return addrForNewConnectionTo(t, s, dstAddr)
}
// addrForNewConnectionWithAddr returns the local address used when creating a
// new connection with a specific local address.
func addrForNewConnectionWithAddr(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
t.Helper()
wq := waiter.Queue{}
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.EventIn)
+109 -6
View File
@@ -15,6 +15,8 @@
package stack
import (
"log"
"sort"
"strings"
"sync/atomic"
@@ -251,13 +253,17 @@ func (n *NIC) setSpoofing(enable bool) {
n.mu.Unlock()
}
// primaryEndpoint returns the primary endpoint of n for the given network
// protocol.
//
// primaryEndpoint will return the first non-deprecated endpoint if such an
// endpoint exists. If no non-deprecated endpoint exists, the first deprecated
// endpoint will be returned.
func (n *NIC) primaryEndpoint(protocol tcpip.NetworkProtocolNumber) *referencedNetworkEndpoint {
// endpoint exists for the given protocol and remoteAddr. If no non-deprecated
// endpoint exists, the first deprecated endpoint will be returned.
//
// If an IPv6 primary endpoint is requested, Source Address Selection (as
// defined by RFC 6724 section 5) will be performed.
func (n *NIC) primaryEndpoint(protocol tcpip.NetworkProtocolNumber, remoteAddr tcpip.Address) *referencedNetworkEndpoint {
if protocol == header.IPv6ProtocolNumber && remoteAddr != "" {
return n.primaryIPv6Endpoint(remoteAddr)
}
n.mu.RLock()
defer n.mu.RUnlock()
@@ -296,6 +302,103 @@ func (n *NIC) primaryEndpoint(protocol tcpip.NetworkProtocolNumber) *referencedN
return deprecatedEndpoint
}
// ipv6AddrCandidate is an IPv6 candidate for Source Address Selection (RFC
// 6724 section 5).
type ipv6AddrCandidate struct {
ref *referencedNetworkEndpoint
scope header.IPv6AddressScope
}
// primaryIPv6Endpoint returns an IPv6 endpoint following Source Address
// Selection (RFC 6724 section 5).
//
// Note, only rules 1-3 are followed.
//
// remoteAddr must be a valid IPv6 address.
func (n *NIC) primaryIPv6Endpoint(remoteAddr tcpip.Address) *referencedNetworkEndpoint {
n.mu.RLock()
defer n.mu.RUnlock()
primaryAddrs := n.primary[header.IPv6ProtocolNumber]
if len(primaryAddrs) == 0 {
return nil
}
// Create a candidate set of available addresses we can potentially use as a
// source address.
cs := make([]ipv6AddrCandidate, 0, len(primaryAddrs))
for _, r := range primaryAddrs {
// If r is not valid for outgoing connections, it is not a valid endpoint.
if !r.isValidForOutgoing() {
continue
}
addr := r.ep.ID().LocalAddress
scope, err := header.ScopeForIPv6Address(addr)
if err != nil {
// Should never happen as we got r from the primary IPv6 endpoint list and
// ScopeForIPv6Address only returns an error if addr is not an IPv6
// address.
log.Fatalf("header.ScopeForIPv6Address(%s): %s", addr, err)
}
cs = append(cs, ipv6AddrCandidate{
ref: r,
scope: scope,
})
}
remoteScope, err := header.ScopeForIPv6Address(remoteAddr)
if err != nil {
// primaryIPv6Endpoint should never be called with an invalid IPv6 address.
log.Fatalf("header.ScopeForIPv6Address(%s): %s", remoteAddr, err)
}
// Sort the addresses as per RFC 6724 section 5 rules 1-3.
//
// TODO(b/146021396): Implement rules 4-8 of RFC 6724 section 5.
sort.Slice(cs, func(i, j int) bool {
sa := cs[i]
sb := cs[j]
// Prefer same address as per RFC 6724 section 5 rule 1.
if sa.ref.ep.ID().LocalAddress == remoteAddr {
return true
}
if sb.ref.ep.ID().LocalAddress == remoteAddr {
return false
}
// Prefer appropriate scope as per RFC 6724 section 5 rule 2.
if sa.scope < sb.scope {
return sa.scope >= remoteScope
} else if sb.scope < sa.scope {
return sb.scope < remoteScope
}
// Avoid deprecated addresses as per RFC 6724 section 5 rule 3.
if saDep, sbDep := sa.ref.deprecated, sb.ref.deprecated; saDep != sbDep {
// If sa is not deprecated, it is preferred over sb.
return sbDep
}
// sa and sb are equal, return the endpoint that is closest to the front of
// the primary endpoint list.
return i < j
})
// Return the most preferred address that can have its reference count
// incremented.
for _, c := range cs {
if r := c.ref; r.tryIncRef() {
return r
}
}
return nil
}
// hasPermanentAddrLocked returns true if n has a permanent (including currently
// tentative) address, addr.
func (n *NIC) hasPermanentAddrLocked(addr tcpip.Address) bool {
+4 -4
View File
@@ -1106,9 +1106,9 @@ func (s *Stack) GetMainNICAddress(id tcpip.NICID, protocol tcpip.NetworkProtocol
return nic.primaryAddress(protocol), nil
}
func (s *Stack) getRefEP(nic *NIC, localAddr tcpip.Address, netProto tcpip.NetworkProtocolNumber) (ref *referencedNetworkEndpoint) {
func (s *Stack) getRefEP(nic *NIC, localAddr, remoteAddr tcpip.Address, netProto tcpip.NetworkProtocolNumber) (ref *referencedNetworkEndpoint) {
if len(localAddr) == 0 {
return nic.primaryEndpoint(netProto)
return nic.primaryEndpoint(netProto, remoteAddr)
}
return nic.findEndpoint(netProto, localAddr, CanBePrimaryEndpoint)
}
@@ -1124,7 +1124,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
needRoute := !(isBroadcast || isMulticast || header.IsV6LinkLocalAddress(remoteAddr))
if id != 0 && !needRoute {
if nic, ok := s.nics[id]; ok {
if ref := s.getRefEP(nic, localAddr, netProto); ref != nil {
if ref := s.getRefEP(nic, localAddr, remoteAddr, netProto); ref != nil {
return makeRoute(netProto, ref.ep.ID().LocalAddress, remoteAddr, nic.linkEP.LinkAddress(), ref, s.handleLocal && !nic.isLoopback(), multicastLoop && !nic.isLoopback()), nil
}
}
@@ -1134,7 +1134,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
continue
}
if nic, ok := s.nics[route.NIC]; ok {
if ref := s.getRefEP(nic, localAddr, netProto); ref != nil {
if ref := s.getRefEP(nic, localAddr, remoteAddr, netProto); ref != nil {
if len(remoteAddr) == 0 {
// If no remote address was provided, then the route
// provided will refer to the link local address.
+152
View File
@@ -35,6 +35,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
const (
@@ -2411,3 +2412,154 @@ func TestNewPEBOnPromotionToPermanent(t *testing.T) {
}
}
}
func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) {
const (
linkLocalAddr1 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
linkLocalAddr2 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
uniqueLocalAddr1 = tcpip.Address("\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
uniqueLocalAddr2 = tcpip.Address("\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
globalAddr1 = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
globalAddr2 = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
nicID = 1
)
// Rule 3 is not tested here, and is instead tested by NDP's AutoGenAddr test.
tests := []struct {
name string
nicAddrs []tcpip.Address
connectAddr tcpip.Address
expectedLocalAddr tcpip.Address
}{
// Test Rule 1 of RFC 6724 section 5.
{
name: "Same Global most preferred (last address)",
nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
connectAddr: globalAddr1,
expectedLocalAddr: globalAddr1,
},
{
name: "Same Global most preferred (first address)",
nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1, uniqueLocalAddr1},
connectAddr: globalAddr1,
expectedLocalAddr: globalAddr1,
},
{
name: "Same Link Local most preferred (last address)",
nicAddrs: []tcpip.Address{globalAddr1, uniqueLocalAddr1, linkLocalAddr1},
connectAddr: linkLocalAddr1,
expectedLocalAddr: linkLocalAddr1,
},
{
name: "Same Link Local most preferred (first address)",
nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
connectAddr: linkLocalAddr1,
expectedLocalAddr: linkLocalAddr1,
},
{
name: "Same Unique Local most preferred (last address)",
nicAddrs: []tcpip.Address{uniqueLocalAddr1, globalAddr1, linkLocalAddr1},
connectAddr: uniqueLocalAddr1,
expectedLocalAddr: uniqueLocalAddr1,
},
{
name: "Same Unique Local most preferred (first address)",
nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1, uniqueLocalAddr1},
connectAddr: uniqueLocalAddr1,
expectedLocalAddr: uniqueLocalAddr1,
},
// Test Rule 2 of RFC 6724 section 5.
{
name: "Global most preferred (last address)",
nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
connectAddr: globalAddr2,
expectedLocalAddr: globalAddr1,
},
{
name: "Global most preferred (first address)",
nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1, uniqueLocalAddr1},
connectAddr: globalAddr2,
expectedLocalAddr: globalAddr1,
},
{
name: "Link Local most preferred (last address)",
nicAddrs: []tcpip.Address{globalAddr1, uniqueLocalAddr1, linkLocalAddr1},
connectAddr: linkLocalAddr2,
expectedLocalAddr: linkLocalAddr1,
},
{
name: "Link Local most preferred (first address)",
nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
connectAddr: linkLocalAddr2,
expectedLocalAddr: linkLocalAddr1,
},
{
name: "Unique Local most preferred (last address)",
nicAddrs: []tcpip.Address{uniqueLocalAddr1, globalAddr1, linkLocalAddr1},
connectAddr: uniqueLocalAddr2,
expectedLocalAddr: uniqueLocalAddr1,
},
{
name: "Unique Local most preferred (first address)",
nicAddrs: []tcpip.Address{globalAddr1, linkLocalAddr1, uniqueLocalAddr1},
connectAddr: uniqueLocalAddr2,
expectedLocalAddr: uniqueLocalAddr1,
},
// Test returning the endpoint that is closest to the front when
// candidate addresses are "equal" from the perspective of RFC 6724
// section 5.
{
name: "Unique Local for Global",
nicAddrs: []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, uniqueLocalAddr2},
connectAddr: globalAddr2,
expectedLocalAddr: uniqueLocalAddr1,
},
{
name: "Link Local for Global",
nicAddrs: []tcpip.Address{linkLocalAddr1, linkLocalAddr2},
connectAddr: globalAddr2,
expectedLocalAddr: linkLocalAddr1,
},
{
name: "Link Local for Unique Local",
nicAddrs: []tcpip.Address{linkLocalAddr1, linkLocalAddr2},
connectAddr: uniqueLocalAddr2,
expectedLocalAddr: linkLocalAddr1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{udp.NewProtocol()},
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
s.SetRouteTable([]tcpip.Route{{
Destination: header.IPv6EmptySubnet,
Gateway: llAddr3,
NIC: nicID,
}})
s.AddLinkAddress(nicID, llAddr3, linkAddr3)
for _, a := range test.nicAddrs {
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, a); err != nil {
t.Errorf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, a, err)
}
}
if t.Failed() {
t.FailNow()
}
if got := addrForNewConnectionTo(t, s, tcpip.FullAddress{Addr: test.connectAddr, NIC: nicID, Port: 1234}); got != test.expectedLocalAddr {
t.Errorf("got local address = %s, want = %s", got, test.expectedLocalAddr)
}
})
}
}