Pass RouteInfo to the route resolve callback

The route resolution callback will be called with a
stack.ResolvedFieldsResult which will hold the route info so callers
can avoid attempting resolution again to check if a previous resolution
attempt succeeded or not.

Test: integration_test.TestRouteResolvedFields
PiperOrigin-RevId: 353319019
This commit is contained in:
Ghanan Gowripalan
2021-01-22 14:25:46 -08:00
committed by gVisor bot
parent f52f0101bb
commit f190e13a74
3 changed files with 170 additions and 11 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ func (f *packetsPendingLinkResolution) enqueue(r *Route, gso *GSO, proto tcpip.N
//
// To make sure B does not interleave with A and C, we make sure A and C are
// done while holding the lock.
routeInfo, ch, err := r.ResolvedFields(nil)
routeInfo, ch, err := r.resolvedFields(nil)
switch err {
case nil:
// The route resolved immediately, so we don't need to wait for link
+34 -10
View File
@@ -315,23 +315,42 @@ func (r *Route) ResolveWith(addr tcpip.LinkAddress) {
r.mu.remoteLinkAddress = addr
}
// ResolvedFields is like Fields but also attempts to resolve the remote link
// address if it is not yet known.
// ResolvedFieldsResult is the result of a route resolution attempt.
type ResolvedFieldsResult struct {
RouteInfo RouteInfo
Success bool
}
// ResolvedFields attempts to resolve the remote link address if it is not
// known.
//
// If address resolution is required, returns tcpip.ErrWouldBlock and a
// notification channel for the caller to block on. The channel will be readable
// once address resolution is complete (successful or not). If a callback is
// provided, it will be called when address resolution is complete, regardless
// of success or failure before the notification channel is readable.
// If a callback is provided, it will be called before ResolvedFields returns
// when address resolution is not required. If address resolution is required,
// the callback will be called once address resolution is complete, regardless
// of success or failure.
//
// Note, the route will not cache the remote link address when address
// resolution completes.
func (r *Route) ResolvedFields(afterResolve func()) (RouteInfo, <-chan struct{}, *tcpip.Error) {
func (r *Route) ResolvedFields(afterResolve func(ResolvedFieldsResult)) *tcpip.Error {
_, _, err := r.resolvedFields(afterResolve)
return err
}
// resolvedFields is like ResolvedFields but also returns a notification channel
// when address resolution is required. This channel will become readable once
// address resolution is complete.
//
// The route's fields will also be returned, regardless of whether address
// resolution is required or not.
func (r *Route) resolvedFields(afterResolve func(ResolvedFieldsResult)) (RouteInfo, <-chan struct{}, *tcpip.Error) {
r.mu.RLock()
fields := r.fieldsLocked()
resolutionRequired := r.isResolutionRequiredRLocked()
r.mu.RUnlock()
if !resolutionRequired {
if afterResolve != nil {
afterResolve(ResolvedFieldsResult{RouteInfo: fields, Success: true})
}
return fields, nil, nil
}
@@ -347,9 +366,14 @@ func (r *Route) ResolvedFields(afterResolve func()) (RouteInfo, <-chan struct{},
linkAddressResolutionRequestLocalAddr = r.LocalAddress
}
linkAddr, ch, err := r.outgoingNIC.getNeighborLinkAddress(nextAddr, linkAddressResolutionRequestLocalAddr, r.linkRes, func(LinkResolutionResult) {
afterResolveFields := fields
linkAddr, ch, err := r.outgoingNIC.getNeighborLinkAddress(nextAddr, linkAddressResolutionRequestLocalAddr, r.linkRes, func(r LinkResolutionResult) {
if afterResolve != nil {
afterResolve()
if r.Success {
afterResolveFields.RemoteLinkAddress = r.LinkAddress
}
afterResolve(ResolvedFieldsResult{RouteInfo: afterResolveFields, Success: r.Success})
}
})
if err == nil {
@@ -471,6 +471,141 @@ func TestGetLinkAddress(t *testing.T) {
}
}
func TestRouteResolvedFields(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
localAddr tcpip.Address
remoteAddr tcpip.Address
immediatelyResolvable bool
expectedSuccess bool
expectedLinkAddr tcpip.LinkAddress
}{
{
name: "IPv4 immediately resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: header.IPv4AllSystems,
immediatelyResolvable: true,
expectedSuccess: true,
expectedLinkAddr: header.EthernetAddressFromMulticastIPv4Address(header.IPv4AllSystems),
},
{
name: "IPv6 immediately resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: header.IPv6AllNodesMulticastAddress,
immediatelyResolvable: true,
expectedSuccess: true,
expectedLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
},
{
name: "IPv4 resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: ipv4Addr2.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedSuccess: true,
expectedLinkAddr: linkAddr2,
},
{
name: "IPv6 resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: ipv6Addr2.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedSuccess: true,
expectedLinkAddr: linkAddr2,
},
{
name: "IPv4 not resolvable",
netProto: ipv4.ProtocolNumber,
localAddr: ipv4Addr1.AddressWithPrefix.Address,
remoteAddr: ipv4Addr3.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedSuccess: false,
},
{
name: "IPv6 not resolvable",
netProto: ipv6.ProtocolNumber,
localAddr: ipv6Addr1.AddressWithPrefix.Address,
remoteAddr: ipv6Addr3.AddressWithPrefix.Address,
immediatelyResolvable: false,
expectedSuccess: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, useNeighborCache := range []bool{true, false} {
t.Run(fmt.Sprintf("UseNeighborCache=%t", useNeighborCache), func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
UseNeighborCache: useNeighborCache,
}
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
r, err := host1Stack.FindRoute(host1NICID, "", test.remoteAddr, test.netProto, false /* multicastLoop */)
if err != nil {
t.Fatalf("host1Stack.FindRoute(%d, '', %s, %d, false): %s", host1NICID, test.remoteAddr, test.netProto, err)
}
defer r.Release()
var wantRouteInfo stack.RouteInfo
wantRouteInfo.LocalLinkAddress = linkAddr1
wantRouteInfo.LocalAddress = test.localAddr
wantRouteInfo.RemoteAddress = test.remoteAddr
wantRouteInfo.NetProto = test.netProto
wantRouteInfo.Loop = stack.PacketOut
wantRouteInfo.RemoteLinkAddress = test.expectedLinkAddr
ch := make(chan stack.ResolvedFieldsResult, 1)
if !test.immediatelyResolvable {
wantUnresolvedRouteInfo := wantRouteInfo
wantUnresolvedRouteInfo.RemoteLinkAddress = ""
if err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
}); err != tcpip.ErrWouldBlock {
t.Errorf("got r.ResolvedFields(_) = %s, want = %s", err, tcpip.ErrWouldBlock)
}
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: test.expectedSuccess}, <-ch, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result mismatch (-want +got):\n%s", diff)
}
if !test.expectedSuccess {
return
}
// At this point the neighbor table should be populated so the route
// should be immediately resolvable.
}
if err := r.ResolvedFields(func(r stack.ResolvedFieldsResult) {
ch <- r
}); err != nil {
t.Errorf("r.ResolvedFields(_): %s", err)
}
select {
case routeResolveRes := <-ch:
if diff := cmp.Diff(stack.ResolvedFieldsResult{RouteInfo: wantRouteInfo, Success: true}, routeResolveRes, cmp.AllowUnexported(stack.RouteInfo{})); diff != "" {
t.Errorf("route resolve result from resolved route mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected route to be immediately resolvable")
}
})
}
})
}
}
func TestWritePacketsLinkResolution(t *testing.T) {
const (
host1NICID = 1