Support disabling a NIC

- Disabled NICs will have their associated NDP state cleared.
- Disabled NICs will not accept incoming packets.
- Writes through a Route with a disabled NIC will return an invalid
  endpoint state error.
- stack.Stack.FindRoute will not return a route with a disabled NIC.
- NIC's Running flag will report the NIC's enabled status.

Tests:
- stack_test.TestDisableUnknownNIC
- stack_test.TestDisabledNICsNICInfoAndCheckNIC
- stack_test.TestRoutesWithDisabledNIC
- stack_test.TestRouteWritePacketWithDisabledNIC
- stack_test.TestStopStartSolicitingRouters
- stack_test.TestCleanupNDPState
- stack_test.TestAddRemoveIPv4BroadcastAddressOnNICEnableDisable
- stack_test.TestJoinLeaveAllNodesMulticastOnNICEnableDisable
PiperOrigin-RevId: 296298588
This commit is contained in:
gVisor bot
2020-02-20 14:32:49 -08:00
committed by Copybara-Service
parent d90d71474f
commit 67b615b86f
5 changed files with 998 additions and 379 deletions
+14 -9
View File
@@ -1148,22 +1148,27 @@ func (ndp *ndpState) cleanupAutoGenAddrResourcesAndNotify(addr tcpip.Address) bo
return true
}
// cleanupHostOnlyState cleans up any state that is only useful for hosts.
// cleanupState cleans up ndp's state.
//
// cleanupHostOnlyState MUST be called when ndp's NIC is transitioning from a
// host to a router. This function will invalidate all discovered on-link
// prefixes, discovered routers, and auto-generated addresses as routers do not
// normally process Router Advertisements to discover default routers and
// on-link prefixes, and auto-generate addresses via SLAAC.
// If hostOnly is true, then only host-specific state will be cleaned up.
//
// cleanupState MUST be called with hostOnly set to true when ndp's NIC is
// transitioning from a host to a router. This function will invalidate all
// discovered on-link prefixes, discovered routers, and auto-generated
// addresses.
//
// If hostOnly is true, then the link-local auto-generated address will not be
// invalidated as routers are also expected to generate a link-local address.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupHostOnlyState() {
func (ndp *ndpState) cleanupState(hostOnly bool) {
linkLocalSubnet := header.IPv6LinkLocalPrefix.Subnet()
linkLocalAddrs := 0
for addr := range ndp.autoGenAddresses {
// RFC 4862 section 5 states that routers are also expected to generate a
// link-local address so we do not invalidate them.
if linkLocalSubnet.Contains(addr) {
// link-local address so we do not invalidate them if we are cleaning up
// host-only state.
if hostOnly && linkLocalSubnet.Contains(addr) {
linkLocalAddrs++
continue
}
File diff suppressed because it is too large Load Diff
+97 -13
View File
@@ -27,6 +27,14 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header"
)
var ipv4BroadcastAddr = tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: header.IPv4Broadcast,
PrefixLen: 8 * header.IPv4AddressSize,
},
}
// NIC represents a "network interface card" to which the networking stack is
// attached.
type NIC struct {
@@ -36,7 +44,8 @@ type NIC struct {
linkEP LinkEndpoint
context NICContext
stats NICStats
stats NICStats
attach sync.Once
mu struct {
sync.RWMutex
@@ -135,7 +144,69 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
return nic
}
// enable enables the NIC. enable will attach the link to its LinkEndpoint and
// enabled returns true if n is enabled.
func (n *NIC) enabled() bool {
n.mu.RLock()
enabled := n.mu.enabled
n.mu.RUnlock()
return enabled
}
// disable disables n.
//
// It undoes the work done by enable.
func (n *NIC) disable() *tcpip.Error {
n.mu.RLock()
enabled := n.mu.enabled
n.mu.RUnlock()
if !enabled {
return nil
}
n.mu.Lock()
defer n.mu.Unlock()
if !n.mu.enabled {
return nil
}
// TODO(b/147015577): Should Routes that are currently bound to n be
// invalidated? Currently, Routes will continue to work when a NIC is enabled
// again, and applications may not know that the underlying NIC was ever
// disabled.
if _, ok := n.stack.networkProtocols[header.IPv6ProtocolNumber]; ok {
n.mu.ndp.stopSolicitingRouters()
n.mu.ndp.cleanupState(false /* hostOnly */)
// Stop DAD for all the unicast IPv6 endpoints that are in the
// permanentTentative state.
for _, r := range n.mu.endpoints {
if addr := r.ep.ID().LocalAddress; r.getKind() == permanentTentative && header.IsV6UnicastAddress(addr) {
n.mu.ndp.stopDuplicateAddressDetection(addr)
}
}
// The NIC may have already left the multicast group.
if err := n.leaveGroupLocked(header.IPv6AllNodesMulticastAddress); err != nil && err != tcpip.ErrBadLocalAddress {
return err
}
}
if _, ok := n.stack.networkProtocols[header.IPv4ProtocolNumber]; ok {
// The address may have already been removed.
if err := n.removePermanentAddressLocked(ipv4BroadcastAddr.AddressWithPrefix.Address); err != nil && err != tcpip.ErrBadLocalAddress {
return err
}
}
// TODO(b/147015577): Should n detach from its LinkEndpoint?
n.mu.enabled = false
return nil
}
// enable enables n. enable will attach the nic to its LinkEndpoint and
// join the IPv6 All-Nodes Multicast address (ff02::1).
func (n *NIC) enable() *tcpip.Error {
n.mu.RLock()
@@ -158,10 +229,7 @@ func (n *NIC) enable() *tcpip.Error {
// Create an endpoint to receive broadcast packets on this interface.
if _, ok := n.stack.networkProtocols[header.IPv4ProtocolNumber]; ok {
if _, err := n.addAddressLocked(tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{header.IPv4Broadcast, 8 * header.IPv4AddressSize},
}, NeverPrimaryEndpoint, permanent, static, false /* deprecated */); err != nil {
if _, err := n.addAddressLocked(ipv4BroadcastAddr, NeverPrimaryEndpoint, permanent, static, false /* deprecated */); err != nil {
return err
}
}
@@ -183,6 +251,14 @@ func (n *NIC) enable() *tcpip.Error {
return nil
}
// Join the All-Nodes multicast group before starting DAD as responses to DAD
// (NDP NS) messages may be sent to the All-Nodes multicast group if the
// source address of the NDP NS is the unspecified address, as per RFC 4861
// section 7.2.4.
if err := n.joinGroupLocked(header.IPv6ProtocolNumber, header.IPv6AllNodesMulticastAddress); err != nil {
return err
}
// Perform DAD on the all the unicast IPv6 endpoints that are in the permanent
// state.
//
@@ -200,10 +276,6 @@ func (n *NIC) enable() *tcpip.Error {
}
}
if err := n.joinGroupLocked(header.IPv6ProtocolNumber, header.IPv6AllNodesMulticastAddress); err != nil {
return err
}
// Do not auto-generate an IPv6 link-local address for loopback devices.
if n.stack.autoGenIPv6LinkLocal && !n.isLoopback() {
// The valid and preferred lifetime is infinite for the auto-generated
@@ -234,7 +306,7 @@ func (n *NIC) becomeIPv6Router() {
n.mu.Lock()
defer n.mu.Unlock()
n.mu.ndp.cleanupHostOnlyState()
n.mu.ndp.cleanupState(true /* hostOnly */)
n.mu.ndp.stopSolicitingRouters()
}
@@ -252,7 +324,9 @@ func (n *NIC) becomeIPv6Host() {
// attachLinkEndpoint attaches the NIC to the endpoint, which will enable it
// to start delivering packets.
func (n *NIC) attachLinkEndpoint() {
n.linkEP.Attach(n)
n.attach.Do(func() {
n.linkEP.Attach(n)
})
}
// setPromiscuousMode enables or disables promiscuous mode.
@@ -712,6 +786,7 @@ func (n *NIC) AllAddresses() []tcpip.ProtocolAddress {
case permanentExpired, temporary:
continue
}
addrs = append(addrs, tcpip.ProtocolAddress{
Protocol: ref.protocol,
AddressWithPrefix: tcpip.AddressWithPrefix{
@@ -1009,6 +1084,15 @@ func (n *NIC) leaveGroupLocked(addr tcpip.Address) *tcpip.Error {
return nil
}
// isInGroup returns true if n has joined the multicast group addr.
func (n *NIC) isInGroup(addr tcpip.Address) bool {
n.mu.RLock()
joins := n.mu.mcastJoins[NetworkEndpointID{addr}]
n.mu.RUnlock()
return joins != 0
}
func handlePacket(protocol tcpip.NetworkProtocolNumber, dst, src tcpip.Address, localLinkAddr, remotelinkAddr tcpip.LinkAddress, ref *referencedNetworkEndpoint, pkt tcpip.PacketBuffer) {
r := makeRoute(protocol, dst, src, localLinkAddr, ref, false /* handleLocal */, false /* multicastLoop */)
r.RemoteLinkAddress = remotelinkAddr
@@ -1411,7 +1495,7 @@ func (r *referencedNetworkEndpoint) isValidForOutgoing() bool {
//
// r's NIC must be read locked.
func (r *referencedNetworkEndpoint) isValidForOutgoingRLocked() bool {
return r.getKind() != permanentExpired || r.nic.mu.spoofing
return r.nic.mu.enabled && (r.getKind() != permanentExpired || r.nic.mu.spoofing)
}
// decRef decrements the ref count and cleans up the endpoint once it reaches
+36 -9
View File
@@ -921,23 +921,38 @@ func (s *Stack) EnableNIC(id tcpip.NICID) *tcpip.Error {
s.mu.RLock()
defer s.mu.RUnlock()
nic := s.nics[id]
if nic == nil {
nic, ok := s.nics[id]
if !ok {
return tcpip.ErrUnknownNICID
}
return nic.enable()
}
// DisableNIC disables the given NIC.
func (s *Stack) DisableNIC(id tcpip.NICID) *tcpip.Error {
s.mu.RLock()
defer s.mu.RUnlock()
nic, ok := s.nics[id]
if !ok {
return tcpip.ErrUnknownNICID
}
return nic.disable()
}
// CheckNIC checks if a NIC is usable.
func (s *Stack) CheckNIC(id tcpip.NICID) bool {
s.mu.RLock()
defer s.mu.RUnlock()
nic, ok := s.nics[id]
s.mu.RUnlock()
if ok {
return nic.linkEP.IsAttached()
if !ok {
return false
}
return false
return nic.enabled()
}
// NICAddressRanges returns a map of NICIDs to their associated subnets.
@@ -989,7 +1004,7 @@ func (s *Stack) NICInfo() map[tcpip.NICID]NICInfo {
for id, nic := range s.nics {
flags := NICStateFlags{
Up: true, // Netstack interfaces are always up.
Running: nic.linkEP.IsAttached(),
Running: nic.enabled(),
Promiscuous: nic.isPromiscuousMode(),
Loopback: nic.isLoopback(),
}
@@ -1151,7 +1166,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
isMulticast := header.IsV4MulticastAddress(remoteAddr) || header.IsV6MulticastAddress(remoteAddr)
needRoute := !(isBroadcast || isMulticast || header.IsV6LinkLocalAddress(remoteAddr))
if id != 0 && !needRoute {
if nic, ok := s.nics[id]; ok {
if nic, ok := s.nics[id]; ok && nic.enabled() {
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
}
@@ -1161,7 +1176,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
if (id != 0 && id != route.NIC) || (len(remoteAddr) != 0 && !route.Destination.Contains(remoteAddr)) {
continue
}
if nic, ok := s.nics[route.NIC]; ok {
if nic, ok := s.nics[route.NIC]; ok && nic.enabled() {
if ref := s.getRefEP(nic, localAddr, remoteAddr, netProto); ref != nil {
if len(remoteAddr) == 0 {
// If no remote address was provided, then the route
@@ -1614,6 +1629,18 @@ func (s *Stack) LeaveGroup(protocol tcpip.NetworkProtocolNumber, nicID tcpip.NIC
return tcpip.ErrUnknownNICID
}
// IsInGroup returns true if the NIC with ID nicID has joined the multicast
// group multicastAddr.
func (s *Stack) IsInGroup(nicID tcpip.NICID, multicastAddr tcpip.Address) (bool, *tcpip.Error) {
s.mu.RLock()
defer s.mu.RUnlock()
if nic, ok := s.nics[nicID]; ok {
return nic.isInGroup(multicastAddr), nil
}
return false, tcpip.ErrUnknownNICID
}
// IPTables returns the stack's iptables.
func (s *Stack) IPTables() iptables.IPTables {
s.tablesMu.RLock()
+375 -2
View File
@@ -33,6 +33,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
@@ -509,6 +510,257 @@ func testNoRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr
}
}
func TestDisableUnknownNIC(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{fakeNetFactory()},
})
if err := s.DisableNIC(1); err != tcpip.ErrUnknownNICID {
t.Fatalf("got s.DisableNIC(1) = %v, want = %s", err, tcpip.ErrUnknownNICID)
}
}
func TestDisabledNICsNICInfoAndCheckNIC(t *testing.T) {
const nicID = 1
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{fakeNetFactory()},
})
e := loopback.New()
nicOpts := stack.NICOptions{Disabled: true}
if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, nicOpts, err)
}
checkNIC := func(enabled bool) {
t.Helper()
allNICInfo := s.NICInfo()
nicInfo, ok := allNICInfo[nicID]
if !ok {
t.Errorf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
} else if nicInfo.Flags.Running != enabled {
t.Errorf("got nicInfo.Flags.Running = %t, want = %t", nicInfo.Flags.Running, enabled)
}
if got := s.CheckNIC(nicID); got != enabled {
t.Errorf("got s.CheckNIC(%d) = %t, want = %t", nicID, got, enabled)
}
}
// NIC should initially report itself as disabled.
checkNIC(false)
if err := s.EnableNIC(nicID); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
}
checkNIC(true)
// If the NIC is not reporting a correct enabled status, we cannot trust the
// next check so end the test here.
if t.Failed() {
t.FailNow()
}
if err := s.DisableNIC(nicID); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
}
checkNIC(false)
}
func TestRoutesWithDisabledNIC(t *testing.T) {
const unspecifiedNIC = 0
const nicID1 = 1
const nicID2 = 2
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{fakeNetFactory()},
})
ep1 := channel.New(0, defaultMTU, "")
if err := s.CreateNIC(nicID1, ep1); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
}
addr1 := tcpip.Address("\x01")
if err := s.AddAddress(nicID1, fakeNetNumber, addr1); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID1, fakeNetNumber, addr1, err)
}
ep2 := channel.New(0, defaultMTU, "")
if err := s.CreateNIC(nicID2, ep2); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
}
addr2 := tcpip.Address("\x02")
if err := s.AddAddress(nicID2, fakeNetNumber, addr2); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, fakeNetNumber, addr2, err)
}
// Set a route table that sends all packets with odd destination
// addresses through the first NIC, and all even destination address
// through the second one.
{
subnet0, err := tcpip.NewSubnet("\x00", "\x01")
if err != nil {
t.Fatal(err)
}
subnet1, err := tcpip.NewSubnet("\x01", "\x01")
if err != nil {
t.Fatal(err)
}
s.SetRouteTable([]tcpip.Route{
{Destination: subnet1, Gateway: "\x00", NIC: nicID1},
{Destination: subnet0, Gateway: "\x00", NIC: nicID2},
})
}
// Test routes to odd address.
testRoute(t, s, unspecifiedNIC, "", "\x05", addr1)
testRoute(t, s, unspecifiedNIC, addr1, "\x05", addr1)
testRoute(t, s, nicID1, addr1, "\x05", addr1)
// Test routes to even address.
testRoute(t, s, unspecifiedNIC, "", "\x06", addr2)
testRoute(t, s, unspecifiedNIC, addr2, "\x06", addr2)
testRoute(t, s, nicID2, addr2, "\x06", addr2)
// Disabling NIC1 should result in no routes to odd addresses. Routes to even
// addresses should continue to be available as NIC2 is still enabled.
if err := s.DisableNIC(nicID1); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID1, err)
}
nic1Dst := tcpip.Address("\x05")
testNoRoute(t, s, unspecifiedNIC, "", nic1Dst)
testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
testNoRoute(t, s, nicID1, addr1, nic1Dst)
nic2Dst := tcpip.Address("\x06")
testRoute(t, s, unspecifiedNIC, "", nic2Dst, addr2)
testRoute(t, s, unspecifiedNIC, addr2, nic2Dst, addr2)
testRoute(t, s, nicID2, addr2, nic2Dst, addr2)
// Disabling NIC2 should result in no routes to even addresses. No route
// should be available to any address as routes to odd addresses were made
// unavailable by disabling NIC1 above.
if err := s.DisableNIC(nicID2); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID2, err)
}
testNoRoute(t, s, unspecifiedNIC, "", nic1Dst)
testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
testNoRoute(t, s, nicID1, addr1, nic1Dst)
testNoRoute(t, s, unspecifiedNIC, "", nic2Dst)
testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
testNoRoute(t, s, nicID2, addr2, nic2Dst)
// Enabling NIC1 should make routes to odd addresses available again. Routes
// to even addresses should continue to be unavailable as NIC2 is still
// disabled.
if err := s.EnableNIC(nicID1); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID1, err)
}
testRoute(t, s, unspecifiedNIC, "", nic1Dst, addr1)
testRoute(t, s, unspecifiedNIC, addr1, nic1Dst, addr1)
testRoute(t, s, nicID1, addr1, nic1Dst, addr1)
testNoRoute(t, s, unspecifiedNIC, "", nic2Dst)
testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
testNoRoute(t, s, nicID2, addr2, nic2Dst)
}
func TestRouteWritePacketWithDisabledNIC(t *testing.T) {
const unspecifiedNIC = 0
const nicID1 = 1
const nicID2 = 2
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{fakeNetFactory()},
})
ep1 := channel.New(1, defaultMTU, "")
if err := s.CreateNIC(nicID1, ep1); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
}
addr1 := tcpip.Address("\x01")
if err := s.AddAddress(nicID1, fakeNetNumber, addr1); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID1, fakeNetNumber, addr1, err)
}
ep2 := channel.New(1, defaultMTU, "")
if err := s.CreateNIC(nicID2, ep2); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
}
addr2 := tcpip.Address("\x02")
if err := s.AddAddress(nicID2, fakeNetNumber, addr2); err != nil {
t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, fakeNetNumber, addr2, err)
}
// Set a route table that sends all packets with odd destination
// addresses through the first NIC, and all even destination address
// through the second one.
{
subnet0, err := tcpip.NewSubnet("\x00", "\x01")
if err != nil {
t.Fatal(err)
}
subnet1, err := tcpip.NewSubnet("\x01", "\x01")
if err != nil {
t.Fatal(err)
}
s.SetRouteTable([]tcpip.Route{
{Destination: subnet1, Gateway: "\x00", NIC: nicID1},
{Destination: subnet0, Gateway: "\x00", NIC: nicID2},
})
}
nic1Dst := tcpip.Address("\x05")
r1, err := s.FindRoute(nicID1, addr1, nic1Dst, fakeNetNumber, false /* multicastLoop */)
if err != nil {
t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID1, addr1, nic1Dst, fakeNetNumber, err)
}
defer r1.Release()
nic2Dst := tcpip.Address("\x06")
r2, err := s.FindRoute(nicID2, addr2, nic2Dst, fakeNetNumber, false /* multicastLoop */)
if err != nil {
t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID2, addr2, nic2Dst, fakeNetNumber, err)
}
defer r2.Release()
// If we failed to get routes r1 or r2, we cannot proceed with the test.
if t.Failed() {
t.FailNow()
}
buf := buffer.View([]byte{1})
testSend(t, r1, ep1, buf)
testSend(t, r2, ep2, buf)
// Writes with Routes that use the disabled NIC1 should fail.
if err := s.DisableNIC(nicID1); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID1, err)
}
testFailingSend(t, r1, ep1, buf, tcpip.ErrInvalidEndpointState)
testSend(t, r2, ep2, buf)
// Writes with Routes that use the disabled NIC2 should fail.
if err := s.DisableNIC(nicID2); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID2, err)
}
testFailingSend(t, r1, ep1, buf, tcpip.ErrInvalidEndpointState)
testFailingSend(t, r2, ep2, buf, tcpip.ErrInvalidEndpointState)
// Writes with Routes that use the re-enabled NIC1 should succeed.
// TODO(b/147015577): Should we instead completely invalidate all Routes that
// were bound to a disabled NIC at some point?
if err := s.EnableNIC(nicID1); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID1, err)
}
testSend(t, r1, ep1, buf)
testFailingSend(t, r2, ep2, buf, tcpip.ErrInvalidEndpointState)
}
func TestRoutes(t *testing.T) {
// Create a stack with the fake network protocol, two nics, and two
// addresses per nic, the first nic has odd address, the second one has
@@ -2173,13 +2425,29 @@ func TestNICAutoGenLinkLocalAddr(t *testing.T) {
e := channel.New(0, 1280, test.linkAddr)
s := stack.New(opts)
nicOpts := stack.NICOptions{Name: test.nicName}
nicOpts := stack.NICOptions{Name: test.nicName, Disabled: true}
if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
}
var expectedMainAddr tcpip.AddressWithPrefix
// A new disabled NIC should not have any address, even if auto generation
// was enabled.
allStackAddrs := s.AllAddresses()
allNICAddrs, ok := allStackAddrs[nicID]
if !ok {
t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
}
if l := len(allNICAddrs); l != 0 {
t.Fatalf("got len(allNICAddrs) = %d, want = 0", l)
}
// Enabling the NIC should attempt auto-generation of a link-local
// address.
if err := s.EnableNIC(nicID); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
}
var expectedMainAddr tcpip.AddressWithPrefix
if test.shouldGen {
expectedMainAddr = tcpip.AddressWithPrefix{
Address: test.expectedAddr,
@@ -2609,6 +2877,111 @@ func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) {
}
}
func TestAddRemoveIPv4BroadcastAddressOnNICEnableDisable(t *testing.T) {
const nicID = 1
e := loopback.New()
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol()},
})
nicOpts := stack.NICOptions{Disabled: true}
if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
}
allStackAddrs := s.AllAddresses()
allNICAddrs, ok := allStackAddrs[nicID]
if !ok {
t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
}
if l := len(allNICAddrs); l != 0 {
t.Fatalf("got len(allNICAddrs) = %d, want = 0", l)
}
// Enabling the NIC should add the IPv4 broadcast address.
if err := s.EnableNIC(nicID); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
}
allStackAddrs = s.AllAddresses()
allNICAddrs, ok = allStackAddrs[nicID]
if !ok {
t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
}
if l := len(allNICAddrs); l != 1 {
t.Fatalf("got len(allNICAddrs) = %d, want = 1", l)
}
want := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: header.IPv4Broadcast,
PrefixLen: 32,
},
}
if allNICAddrs[0] != want {
t.Fatalf("got allNICAddrs[0] = %+v, want = %+v", allNICAddrs[0], want)
}
// Disabling the NIC should remove the IPv4 broadcast address.
if err := s.DisableNIC(nicID); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
}
allStackAddrs = s.AllAddresses()
allNICAddrs, ok = allStackAddrs[nicID]
if !ok {
t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
}
if l := len(allNICAddrs); l != 0 {
t.Fatalf("got len(allNICAddrs) = %d, want = 0", l)
}
}
func TestJoinLeaveAllNodesMulticastOnNICEnableDisable(t *testing.T) {
const nicID = 1
e := loopback.New()
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
})
nicOpts := stack.NICOptions{Disabled: true}
if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
}
// Should not be in the IPv6 all-nodes multicast group yet because the NIC has
// not been enabled yet.
isInGroup, err := s.IsInGroup(nicID, header.IPv6AllNodesMulticastAddress)
if err != nil {
t.Fatalf("IsInGroup(%d, %s): %s", nicID, header.IPv6AllNodesMulticastAddress, err)
}
if isInGroup {
t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, header.IPv6AllNodesMulticastAddress)
}
// The all-nodes multicast group should be joined when the NIC is enabled.
if err := s.EnableNIC(nicID); err != nil {
t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
}
isInGroup, err = s.IsInGroup(nicID, header.IPv6AllNodesMulticastAddress)
if err != nil {
t.Fatalf("IsInGroup(%d, %s): %s", nicID, header.IPv6AllNodesMulticastAddress, err)
}
if !isInGroup {
t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, header.IPv6AllNodesMulticastAddress)
}
// The all-nodes multicast group should be left when the NIC is disabled.
if err := s.DisableNIC(nicID); err != nil {
t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
}
isInGroup, err = s.IsInGroup(nicID, header.IPv6AllNodesMulticastAddress)
if err != nil {
t.Fatalf("IsInGroup(%d, %s): %s", nicID, header.IPv6AllNodesMulticastAddress, err)
}
if isInGroup {
t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, header.IPv6AllNodesMulticastAddress)
}
}
// TestDoDADWhenNICEnabled tests that IPv6 endpoints that were added while a NIC
// was disabled have DAD performed on them when the NIC is enabled.
func TestDoDADWhenNICEnabled(t *testing.T) {