Bind loopback subnets' lifetime to perm address

The lifetime of addreses in a loopback interface's associated subnets
should be bound to their respective permanent addresses.

This change also fixes a race when the stack attempts to get an IPv4
rereferencedNetworkEndpoint for an address in an associated subnet on
a loopback interface. Before this change, the stack would only check
if an IPv4 address is contained in an associated subnet while holding
a read lock but wouldn't do this same check after releasing the read
lock for a write lock to create a temporary address. This may cause
the stack to bind the lifetime of the address to a new (temporary)
endpoint instead of the associated subnet's permanent address.

Test: integration_test.TestLoopbackSubnetLifetimeBoundToAddr
PiperOrigin-RevId: 332094719
This commit is contained in:
Ghanan Gowripalan
2020-09-16 14:56:52 -07:00
committed by gVisor bot
parent 3749e70a69
commit 29ce0ad160
2 changed files with 73 additions and 32 deletions
+12 -32
View File
@@ -665,33 +665,15 @@ func (n *NIC) getRefOrCreateTemp(protocol tcpip.NetworkProtocolNumber, address t
}
}
// Check if address is a broadcast address for the endpoint's network.
//
// Only IPv4 has a notion of broadcast addresses.
if protocol == header.IPv4ProtocolNumber {
if ref := n.getRefForBroadcastRLocked(address); ref != nil {
if ref := n.getIPv4RefForBroadcastOrLoopbackRLocked(address); ref != nil {
n.mu.RUnlock()
return ref
}
}
// A usable reference was not found, create a temporary one if requested by
// the caller or if the IPv4 address is found in the NIC's subnets and the NIC
// is a loopback interface.
createTempEP := spoofingOrPromiscuous
if !createTempEP && n.isLoopback() && protocol == header.IPv4ProtocolNumber {
for _, r := range n.mu.endpoints {
addr := r.addrWithPrefix()
subnet := addr.Subnet()
if subnet.Contains(address) {
createTempEP = true
break
}
}
}
n.mu.RUnlock()
if !createTempEP {
if !spoofingOrPromiscuous {
return nil
}
@@ -704,20 +686,21 @@ func (n *NIC) getRefOrCreateTemp(protocol tcpip.NetworkProtocolNumber, address t
return ref
}
// getRefForBroadcastLocked returns an endpoint where address is the IPv4
// broadcast address for the endpoint's network.
// getRefForBroadcastOrLoopbackRLocked returns an endpoint whose address is the
// broadcast address for the endpoint's network or an address in the endpoint's
// subnet if the NIC is a loopback interface. This matches linux behaviour.
//
// n.mu MUST be read locked.
func (n *NIC) getRefForBroadcastRLocked(address tcpip.Address) *referencedNetworkEndpoint {
// n.mu MUST be read or write locked.
func (n *NIC) getIPv4RefForBroadcastOrLoopbackRLocked(address tcpip.Address) *referencedNetworkEndpoint {
for _, ref := range n.mu.endpoints {
// Only IPv4 has a notion of broadcast addresses.
// Only IPv4 has a notion of broadcast addresses or considers the loopback
// interface bound to an address's whole subnet (on linux).
if ref.protocol != header.IPv4ProtocolNumber {
continue
}
addr := ref.addrWithPrefix()
subnet := addr.Subnet()
if subnet.IsBroadcast(address) && ref.tryIncRef() {
subnet := ref.addrWithPrefix().Subnet()
if (subnet.IsBroadcast(address) || (n.isLoopback() && subnet.Contains(address))) && ref.isValidForOutgoingRLocked() && ref.tryIncRef() {
return ref
}
}
@@ -745,11 +728,8 @@ func (n *NIC) getRefOrCreateTempLocked(protocol tcpip.NetworkProtocolNumber, add
n.removeEndpointLocked(ref)
}
// Check if address is a broadcast address for an endpoint's network.
//
// Only IPv4 has a notion of broadcast addresses.
if protocol == header.IPv4ProtocolNumber {
if ref := n.getRefForBroadcastRLocked(address); ref != nil {
if ref := n.getIPv4RefForBroadcastOrLoopbackRLocked(address); ref != nil {
return ref
}
}
@@ -187,3 +187,64 @@ func TestLoopbackAcceptAllInSubnet(t *testing.T) {
})
}
}
// TestLoopbackSubnetLifetimeBoundToAddr tests that the lifetime of an address
// in a loopback interface's associated subnet is bound to the permanently bound
// address.
func TestLoopbackSubnetLifetimeBoundToAddr(t *testing.T) {
const nicID = 1
protoAddr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: ipv4Addr,
}
addrBytes := []byte(ipv4Addr.Address)
addrBytes[len(addrBytes)-1]++
otherAddr := tcpip.Address(addrBytes)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol()},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
}
if err := s.AddProtocolAddress(nicID, protoAddr); err != nil {
t.Fatalf("s.AddProtocolAddress(%d, %#v): %s", nicID, protoAddr, err)
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv4EmptySubnet,
NIC: nicID,
},
})
r, err := s.FindRoute(nicID, otherAddr, remoteIPv4Addr, ipv4.ProtocolNumber, false /* multicastLoop */)
if err != nil {
t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, otherAddr, remoteIPv4Addr, ipv4.ProtocolNumber, err)
}
defer r.Release()
params := stack.NetworkHeaderParams{
Protocol: 111,
TTL: 64,
TOS: stack.DefaultTOS,
}
data := buffer.View([]byte{1, 2, 3, 4})
if err := r.WritePacket(nil /* gso */, params, stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Data: data.ToVectorisedView(),
})); err != nil {
t.Fatalf("r.WritePacket(nil, %#v, _): %s", params, err)
}
// Removing the address should make the endpoint invalid.
if err := s.RemoveAddress(nicID, protoAddr.AddressWithPrefix.Address); err != nil {
t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, protoAddr.AddressWithPrefix.Address, err)
}
if err := r.WritePacket(nil /* gso */, params, stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Data: data.ToVectorisedView(),
})); err != tcpip.ErrInvalidEndpointState {
t.Fatalf("got r.WritePacket(nil, %#v, _) = %s, want = %s", params, err, tcpip.ErrInvalidEndpointState)
}
}