Use interface-specific NDP configurations instead of the stack-wide default.

This change makes it so that NDP work is done using the per-interface NDP
configurations instead of the stack-wide default NDP configurations to correctly
implement RFC 4861 section 6.3.2 (note here, a host is a single NIC operating
as a host device), and RFC 4862 section 5.1.

Test: Test that we can set NDP configurations on a per-interface basis without
affecting the configurations of other interfaces or the stack-wide default. Also
make sure that after the configurations are updated, the updated configurations
are used for NDP processes (e.g. Duplicate Address Detection).
PiperOrigin-RevId: 276525661
diff --git a/tcpip/stack/ndp.go b/tcpip/stack/ndp.go
index 40e2684..483ae69 100644
--- a/tcpip/stack/ndp.go
+++ b/tcpip/stack/ndp.go
@@ -107,6 +107,9 @@
 	// The NIC this ndpState is for.
 	nic *NIC
 
+	// configs is the per-interface NDP configurations.
+	configs NDPConfigurations
+
 	// The DAD state to send the next NS message, or resolve the address.
 	dad map[tcpip.Address]dadState
 }
@@ -149,7 +152,7 @@
 		panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.nic.ID()))
 	}
 
-	remaining := ndp.nic.stack.ndpConfigs.DupAddrDetectTransmits
+	remaining := ndp.configs.DupAddrDetectTransmits
 
 	{
 		done, err := ndp.doDuplicateAddressDetection(addr, remaining, ref)
@@ -165,7 +168,7 @@
 
 	var done bool
 	var timer *time.Timer
-	timer = time.AfterFunc(ndp.nic.stack.ndpConfigs.RetransmitTimer, func() {
+	timer = time.AfterFunc(ndp.configs.RetransmitTimer, func() {
 		var d bool
 		var err *tcpip.Error
 
@@ -218,7 +221,6 @@
 		if doDadIteration() && ndp.nic.stack.ndpDisp != nil {
 			ndp.nic.stack.ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, d, err)
 		}
-
 	})
 
 	ndp.dad[addr] = dadState{
diff --git a/tcpip/stack/ndp_test.go b/tcpip/stack/ndp_test.go
index 62b6833..e89d1bd 100644
--- a/tcpip/stack/ndp_test.go
+++ b/tcpip/stack/ndp_test.go
@@ -31,6 +31,7 @@
 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"
+	addr3     = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"
 	linkAddr1 = "\x02\x02\x03\x04\x05\x06"
 )
 
@@ -441,3 +442,168 @@
 		t.Fatalf("got NeighborSolicit = %d, want <= 1", got)
 	}
 }
+
+// TestSetNDPConfigurationFailsForBadNICID tests to make sure we get an error if
+// we attempt to update NDP configurations using an invalid NICID.
+func TestSetNDPConfigurationFailsForBadNICID(t *testing.T) {
+	s := stack.New(stack.Options{
+		NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
+	})
+
+	// No NIC with ID 1 yet.
+	if got := s.SetNDPConfigurations(1, stack.NDPConfigurations{}); got != tcpip.ErrUnknownNICID {
+		t.Fatalf("got s.SetNDPConfigurations = %v, want = %s", got, tcpip.ErrUnknownNICID)
+	}
+}
+
+// TestSetNDPConfigurations tests that we can update and use per-interface NDP
+// configurations without affecting the default NDP configurations or other
+// interfaces' configurations.
+func TestSetNDPConfigurations(t *testing.T) {
+	tests := []struct {
+		name                    string
+		dupAddrDetectTransmits  uint8
+		retransmitTimer         time.Duration
+		expectedRetransmitTimer time.Duration
+	}{
+		{
+			"OK",
+			1,
+			time.Second,
+			time.Second,
+		},
+		{
+			"Invalid Retransmit Timer",
+			1,
+			0,
+			time.Second,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			ndpDisp := ndpDispatcher{
+				dadC: make(chan ndpDADEvent),
+			}
+			e := channel.New(10, 1280, linkAddr1)
+			s := stack.New(stack.Options{
+				NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
+				NDPDisp:          &ndpDisp,
+			})
+
+			// This NIC(1)'s NDP configurations will be updated to
+			// be different from the default.
+			if err := s.CreateNIC(1, e); err != nil {
+				t.Fatalf("CreateNIC(1) = %s", err)
+			}
+
+			// Created before updating NIC(1)'s NDP configurations
+			// but updating NIC(1)'s NDP configurations should not
+			// affect other existing NICs.
+			if err := s.CreateNIC(2, e); err != nil {
+				t.Fatalf("CreateNIC(2) = %s", err)
+			}
+
+			// Update the NDP configurations on NIC(1) to use DAD.
+			configs := stack.NDPConfigurations{
+				DupAddrDetectTransmits: test.dupAddrDetectTransmits,
+				RetransmitTimer:        test.retransmitTimer,
+			}
+			if err := s.SetNDPConfigurations(1, configs); err != nil {
+				t.Fatalf("got SetNDPConfigurations(1, _) = %s", err)
+			}
+
+			// Created after updating NIC(1)'s NDP configurations
+			// but the stack's default NDP configurations should not
+			// have been updated.
+			if err := s.CreateNIC(3, e); err != nil {
+				t.Fatalf("CreateNIC(3) = %s", err)
+			}
+
+			// Add addresses for each NIC.
+			if err := s.AddAddress(1, header.IPv6ProtocolNumber, addr1); err != nil {
+				t.Fatalf("AddAddress(1, %d, %s) = %s", header.IPv6ProtocolNumber, addr1, err)
+			}
+			if err := s.AddAddress(2, header.IPv6ProtocolNumber, addr2); err != nil {
+				t.Fatalf("AddAddress(2, %d, %s) = %s", header.IPv6ProtocolNumber, addr2, err)
+			}
+			if err := s.AddAddress(3, header.IPv6ProtocolNumber, addr3); err != nil {
+				t.Fatalf("AddAddress(3, %d, %s) = %s", header.IPv6ProtocolNumber, addr3, err)
+			}
+
+			// Address should not be considered bound to NIC(1) 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)
+			}
+
+			// Should get the address on NIC(2) and NIC(3)
+			// immediately since we should not have performed DAD on
+			// it as the stack was configured to not do DAD by
+			// default and we only updated the NDP configurations on
+			// NIC(1).
+			addr, err = s.GetMainNICAddress(2, header.IPv6ProtocolNumber)
+			if err != nil {
+				t.Fatalf("stack.GetMainNICAddress(2, _) err = %s", err)
+			}
+			if addr.Address != addr2 {
+				t.Fatalf("got stack.GetMainNICAddress(2, _) = %s, want = %s", addr, addr2)
+			}
+			addr, err = s.GetMainNICAddress(3, header.IPv6ProtocolNumber)
+			if err != nil {
+				t.Fatalf("stack.GetMainNICAddress(3, _) err = %s", err)
+			}
+			if addr.Address != addr3 {
+				t.Fatalf("got stack.GetMainNICAddress(3, _) = %s, want = %s", addr, addr3)
+			}
+
+			// Sleep until right (500ms before) before resolution to
+			// make sure the address didn't resolve on NIC(1) yet.
+			const delta = 500 * time.Millisecond
+			time.Sleep(time.Duration(test.dupAddrDetectTransmits)*test.expectedRetransmitTimer - delta)
+			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 DAD to resolve.
+			select {
+			case <-time.After(2 * delta):
+				// We should get a resolution event after 500ms
+				// (delta) since we wait for 500ms less than the
+				// expected resolution time above to make sure
+				// that the address did not yet resolve. Waiting
+				// for 1s (2x delta) without a resolution event
+				// means something is wrong.
+				t.Fatal("timed out waiting for DAD resolution")
+			case e := <-ndpDisp.dadC:
+				if e.err != nil {
+					t.Fatal("got DAD error: ", e.err)
+				}
+				if e.nicid != 1 {
+					t.Fatalf("got DAD event w/ nicid = %d, want = 1", e.nicid)
+				}
+				if e.addr != addr1 {
+					t.Fatalf("got DAD event w/ addr = %s, want = %s", addr, addr1)
+				}
+				if !e.resolved {
+					t.Fatal("got DAD event w/ resolved = false, want = true")
+				}
+			}
+			addr, err = s.GetMainNICAddress(1, header.IPv6ProtocolNumber)
+			if err != nil {
+				t.Fatalf("stack.GetMainNICAddress(1, _) err = %s", err)
+			}
+			if addr.Address != addr1 {
+				t.Fatalf("got stack.GetMainNICAddress(1, _) = %s, want = %s", addr, addr1)
+			}
+		})
+	}
+}
diff --git a/tcpip/stack/nic.go b/tcpip/stack/nic.go
index 232e20c..cad6b91 100644
--- a/tcpip/stack/nic.go
+++ b/tcpip/stack/nic.go
@@ -46,6 +46,10 @@
 
 	stats NICStats
 
+	// ndp is the NDP related state for NIC.
+	//
+	// Note, read and write operations on ndp require that the NIC is
+	// appropriately locked.
 	ndp ndpState
 }
 
@@ -80,6 +84,7 @@
 	NeverPrimaryEndpoint
 )
 
+// newNIC returns a new NIC using the default NDP configurations from stack.
 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
@@ -105,7 +110,8 @@
 			},
 		},
 		ndp: ndpState{
-			dad: make(map[tcpip.Address]dadState),
+			configs: stack.ndpConfigs,
+			dad:     make(map[tcpip.Address]dadState),
 		},
 	}
 	nic.ndp.nic = nic
@@ -937,6 +943,18 @@
 	return n.removePermanentAddressLocked(addr)
 }
 
+// setNDPConfigs sets the NDP configurations for n.
+//
+// Note, if c contains invalid NDP configuration values, it will be fixed to
+// use default values for the erroneous values.
+func (n *NIC) setNDPConfigs(c NDPConfigurations) {
+	c.validate()
+
+	n.mu.Lock()
+	n.ndp.configs = c
+	n.mu.Unlock()
+}
+
 type networkEndpointKind int32
 
 const (
diff --git a/tcpip/stack/stack.go b/tcpip/stack/stack.go
index 32b8de8..54a0a6c 100644
--- a/tcpip/stack/stack.go
+++ b/tcpip/stack/stack.go
@@ -399,7 +399,7 @@
 	// TODO(gvisor.dev/issue/940): S/R this field.
 	portSeed uint32
 
-	// ndpConfigs is the NDP configurations used by interfaces.
+	// ndpConfigs is the default NDP configurations used by interfaces.
 	ndpConfigs NDPConfigurations
 
 	// autoGenIPv6LinkLocal determines whether or not the stack will attempt
@@ -433,7 +433,7 @@
 	// stack (false).
 	HandleLocal bool
 
-	// NDPConfigs is the NDP configurations used by interfaces.
+	// NDPConfigs is the default NDP configurations used by interfaces.
 	//
 	// By default, NDPConfigs will have a zero value for its
 	// DupAddrDetectTransmits field, implying that DAD will not be performed
@@ -1425,6 +1425,25 @@
 	return nic.dupTentativeAddrDetected(addr)
 }
 
+// SetNDPConfigurations sets the per-interface NDP configurations on the NIC
+// with ID id to c.
+//
+// Note, if c contains invalid NDP configuration values, it will be fixed to
+// use default values for the erroneous values.
+func (s *Stack) SetNDPConfigurations(id tcpip.NICID, c NDPConfigurations) *tcpip.Error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	nic, ok := s.nics[id]
+	if !ok {
+		return tcpip.ErrUnknownNICID
+	}
+
+	nic.setNDPConfigs(c)
+
+	return nil
+}
+
 // PortSeed returns a 32 bit value that can be used as a seed value for port
 // picking.
 //