blob: 008c9fa34fd39f55aa52debaf0c3d9c680883d1c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//go:build !build_with_native_toolchain
package netstack
import (
"context"
"fmt"
"strings"
"sync/atomic"
"syscall/zx"
"syscall/zx/fidl"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routes"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routetypes"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/sync"
"go.fuchsia.dev/fuchsia/src/lib/component"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/net"
fnetRoutes "fidl/fuchsia/net/routes"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const routesFidlName = "fuchsia.net.routes"
const watcherV4ProtocolName = "fuchsia.net.routes.WatcherV4"
const watcherV6ProtocolName = "fuchsia.net.routes.WatcherV6"
var _ fnetRoutes.StateWithCtx = (*resolveImpl)(nil)
// resolveImpl provides an implementation for fuchsia.net.routes/State.
type resolveImpl struct {
stack *stack.Stack
}
var _ fnetRoutes.StateV4WithCtx = (*getWatcherImpl)(nil)
var _ fnetRoutes.StateV6WithCtx = (*getWatcherImpl)(nil)
// getWatcherImpl provides implementations for fuchsia.net.routes/StateV4 and
// fuchsia.net.routes/State.V6.
type getWatcherImpl struct {
interruptChan chan<- routeInterrupt
}
func (r *resolveImpl) Resolve(ctx fidl.Context, destination net.IpAddress) (fnetRoutes.StateResolveResult, error) {
const unspecifiedNIC = tcpip.NICID(0)
var unspecifiedLocalAddress tcpip.Address
remote, proto := fidlconv.ToTCPIPAddressAndProtocolNumber(destination)
netProtoName := strings.TrimPrefix(networkProtocolToString(proto), "IP")
var flags string
syslogFn := syslog.DebugTf
if remote.Unspecified() {
flags = "U"
syslogFn = syslog.InfoTf
}
logFn := func(suffix string, err tcpip.Error) {
_ = syslogFn(
routesFidlName, "stack.FindRoute(%s (%s|%s))%s = (_, %s)",
remote,
netProtoName,
flags,
suffix,
err,
)
}
route, err := r.stack.FindRoute(unspecifiedNIC, unspecifiedLocalAddress, remote, proto, false /* multicastLoop */)
if err != nil {
logFn("", err)
return fnetRoutes.StateResolveResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
defer route.Release()
return func() fnetRoutes.StateResolveResult {
ch := make(chan stack.ResolvedFieldsResult, 1)
err := route.ResolvedFields(func(result stack.ResolvedFieldsResult) {
ch <- result
})
switch err.(type) {
case nil, *tcpip.ErrWouldBlock:
select {
case result := <-ch:
if result.Err == nil {
// Build our response with the resolved route.
nicID := route.NICID()
route := result.RouteInfo
var node fnetRoutes.Destination
node.SetSourceAddress(fidlconv.ToNetIpAddress(route.LocalAddress))
// If the remote link address is unspecified, then the outgoing link
// does not support MAC addressing.
if linkAddr := route.RemoteLinkAddress; len(linkAddr) != 0 {
node.SetMac(fidlconv.ToNetMacAddress(linkAddr))
}
node.SetInterfaceId(uint64(nicID))
var response fnetRoutes.StateResolveResponse
if route.NextHop.Len() != 0 {
node.SetAddress(fidlconv.ToNetIpAddress(route.NextHop))
response.Result.SetGateway(node)
} else {
node.SetAddress(fidlconv.ToNetIpAddress(route.RemoteAddress))
response.Result.SetDirect(node)
}
return fnetRoutes.StateResolveResultWithResponse(response)
}
err = result.Err
case <-ctx.Done():
switch ctx.Err() {
case context.Canceled:
return fnetRoutes.StateResolveResultWithErr(int32(zx.ErrCanceled))
case context.DeadlineExceeded:
return fnetRoutes.StateResolveResultWithErr(int32(zx.ErrTimedOut))
}
}
}
logFn(".ResolvedFields(...)", err)
return fnetRoutes.StateResolveResultWithErr(int32(zx.ErrAddressUnreachable))
}(), nil
}
// GetRouteTableName implements fuchsia.net.routes/State.GetRouteTableName.
func (r *resolveImpl) GetRouteTableName(ctx_ fidl.Context, tableId uint32) (fnetRoutes.StateGetRouteTableNameResult, error) {
panic("TODO(https://fxbug.dev/336205291): Implement for main table")
}
// GetWatcherV4 implements fuchsia.net.routes/StateV4.GetWatcherV4.
func (r *getWatcherImpl) GetWatcherV4(
_ fidl.Context,
watcher fnetRoutes.WatcherV4WithCtxInterfaceRequest,
options fnetRoutes.WatcherOptionsV4,
) error {
r.interruptChan <- &getWatcherV4Request{
req: watcher,
options: options,
}
return nil
}
// GetRuleWatcherV4 implements fuchsia.net.routes/StateV4.GetRuleWatcherV4.
func (r *getWatcherImpl) GetRuleWatcherV4(
_ fidl.Context,
watcher fnetRoutes.RuleWatcherV4WithCtxInterfaceRequest,
options fnetRoutes.RuleWatcherOptionsV4,
) error {
panic("TODO(https://fxbug.dev/336204757): Implement rules watcher")
}
// GetWatcherV6 implements fuchsia.net.routes/StateV6.GetWatcherV6.
func (r *getWatcherImpl) GetWatcherV6(
_ fidl.Context,
watcher fnetRoutes.WatcherV6WithCtxInterfaceRequest,
options fnetRoutes.WatcherOptionsV6,
) error {
r.interruptChan <- &getWatcherV6Request{
req: watcher,
options: options,
}
return nil
}
// GetRuleWatcherV6 implements fuchsia.net.routes/StateV6.GetRuleWatcherV6.
func (r *getWatcherImpl) GetRuleWatcherV6(
_ fidl.Context,
watcher fnetRoutes.RuleWatcherV6WithCtxInterfaceRequest,
options fnetRoutes.RuleWatcherOptionsV6,
) error {
panic("TODO(https://fxbug.dev/336204757): Implement rules watcher")
}
// routesGetWatcherRequest is an interface for GetWatcher requests, abstracting
// over the V4 & V6 variants.
type routesGetWatcherRequest interface {
// serve serves the GetWatcher request by instantiating a Watcher client.
serve(
ctx context.Context,
cancel context.CancelFunc,
existingRoutes map[installedRouteComparisonKey]struct{},
eventsChan chan eventUnion,
onClose chan<- routeInterrupt,
metrics *fidlRoutesWatcherMetrics,
) *routesWatcherImplInner
}
var _ routesGetWatcherRequest = (*getWatcherV4Request)(nil)
var _ routesGetWatcherRequest = (*getWatcherV6Request)(nil)
// getWatcherV4Request is an implementation of the routesGetWatcherRequest
// interface for IPv4.
type getWatcherV4Request struct {
req fnetRoutes.WatcherV4WithCtxInterfaceRequest
options fnetRoutes.WatcherOptionsV4
}
// getWatcherV6Request is an implementation of the routesGetWatcherRequest
// interface for IPv6.
type getWatcherV6Request struct {
req fnetRoutes.WatcherV6WithCtxInterfaceRequest
options fnetRoutes.WatcherOptionsV6
}
// serve implements routesGetWatcherRequest.
func (r *getWatcherV4Request) serve(
ctx context.Context,
cancel context.CancelFunc,
existingRoutes map[installedRouteComparisonKey]struct{},
eventsChan chan eventUnion,
onClose chan<- routeInterrupt,
metrics *fidlRoutesWatcherMetrics,
) *routesWatcherImplInner {
impl := routesWatcherV4Impl{
inner: makeRoutesWatcherImplInner(
cancel,
eventsChan,
// Filter out all Non-IPv4 events.
func(e *eventUnion) bool {
return e.version == routetypes.IPv4
},
existingRoutes,
eventUnion{
version: routetypes.IPv4,
ipv4_event: fnetRoutes.EventV4WithIdle(fnetRoutes.Empty{}),
},
),
}
go func() {
defer func() {
metrics.count_v4.Add(-1)
cancel()
onClose <- &(impl.inner)
}()
metrics.count_v4.Add(1)
component.Serve(
ctx,
&fnetRoutes.WatcherV4WithCtxStub{Impl: &impl},
r.req.Channel,
component.ServeOptions{
Concurrent: true,
OnError: func(err error) {
_ = syslog.WarnTf(watcherV4ProtocolName, "%s", err)
},
})
}()
return &(impl.inner)
}
// serve implements routesGetWatcherRequest.
func (r *getWatcherV6Request) serve(
ctx context.Context,
cancel context.CancelFunc,
existingRoutes map[installedRouteComparisonKey]struct{},
eventsChan chan eventUnion,
onClose chan<- routeInterrupt,
metrics *fidlRoutesWatcherMetrics,
) *routesWatcherImplInner {
impl := routesWatcherV6Impl{
inner: makeRoutesWatcherImplInner(
cancel,
eventsChan,
// Filter out all Non-IPv6 events.
func(e *eventUnion) bool {
return e.version == routetypes.IPv6
},
existingRoutes,
eventUnion{
version: routetypes.IPv6,
ipv6_event: fnetRoutes.EventV6WithIdle(fnetRoutes.Empty{}),
},
),
}
go func() {
defer func() {
metrics.count_v6.Add(-1)
cancel()
onClose <- &(impl.inner)
}()
metrics.count_v6.Add(1)
component.Serve(
ctx,
&fnetRoutes.WatcherV6WithCtxStub{Impl: &impl},
r.req.Channel,
component.ServeOptions{
Concurrent: true,
OnError: func(err error) {
_ = syslog.WarnTf(watcherV6ProtocolName, "%s", err)
},
})
}()
return &(impl.inner)
}
// isRouteInterrupt implements routeInterrupt.
func (*getWatcherV4Request) isRouteInterrupt() {}
// isRouteInterrupt implements routeInterrupt.
func (*getWatcherV6Request) isRouteInterrupt() {}
// routesWatcherImplInner is the implementation of a routes Watcher protocol.
type routesWatcherImplInner struct {
// cancel is the function to call to cancel the watcher.
cancel context.CancelFunc
// eventsChan is the channel of events that should be sent to this watcher's
// client.
eventsChan chan eventUnion
// existingEvents are the routes that existed at the time this watcher
// client was instantiated, followed by the idle sentinel. Note that
// existing routes are stored out of band from eventsChan, because the
// number of existing routes may exceed the size of the eventsChan.
existingEvents []eventUnion
// filter is the predicate applied to events before they are pushed into
// eventsChan.
filter func(*eventUnion) bool
mu struct {
sync.Mutex
// isHanging is True while a Watch request is in progress. E.g.
// "hanging" in the Hanging-Get design pattern.
isHanging bool
}
}
// makeRoutesWatcherImplInner constructs a new routesWatcherImplInner.
func makeRoutesWatcherImplInner(
cancel context.CancelFunc,
eventsChan chan eventUnion,
filter func(*eventUnion) bool,
existingRoutes map[installedRouteComparisonKey]struct{},
idleEvent eventUnion,
) routesWatcherImplInner {
// Create the existing events.
var existingEvents []eventUnion
for key := range existingRoutes {
installedRoute, err := fromInstalledRouteComparisonKey(key)
if err != nil {
panic(fmt.Sprintf("observed error while attempting to construct route from comparison key %#v: %s", key, err))
}
event := toEvent(existingEvent, installedRoute)
if filter(&event) {
existingEvents = append(existingEvents, event)
}
}
existingEvents = append(existingEvents, idleEvent)
return routesWatcherImplInner{
cancel: cancel,
eventsChan: eventsChan,
filter: filter,
existingEvents: existingEvents,
}
}
// pushEvent pushes the given event into the watcher's event channel, if it
// passes the watcher's filter. If the push would block (indicating the client
// isn't keeping up with the flow of events), the client is canceled.
func (w *routesWatcherImplInner) pushEvent(e eventUnion) {
if !w.filter(&e) {
return
}
select {
case w.eventsChan <- e:
// The event was successfully dispatched without blocking.
default:
_ = syslog.WarnTf(
routesFidlName,
"too many unconsumed events "+
"(the client may not be calling Watch frequently enough): %d",
maxPendingEventsPerClient,
)
w.cancel()
}
}
// pullEvents pulls events from the underlying eventsChan. This call blocks if
// there are no available events. Otherwise, this call pulls all available
// events from the underlying channel (up to fnetRoutes.MaxEvents) and returns
// immediately.
func (w *routesWatcherImplInner) pullEvents(fidlCtx fidl.Context) ([]eventUnion, error) {
var events []eventUnion
for len(events) < int(fnetRoutes.MaxEvents) {
if len(w.existingEvents) > 0 {
// First drain the existing routes, before pulling from the channel.
var existing eventUnion
existing, w.existingEvents = w.existingEvents[0], w.existingEvents[1:]
events = append(events, existing)
} else if len(events) != 0 {
// Poll the channel in a non-blocking fashion if we've already
// accumulated some events.
select {
case <-fidlCtx.Done():
return nil, fmt.Errorf("cancelled: %w", fidlCtx.Err())
case event := <-w.eventsChan:
events = append(events, event)
default:
return events, nil
}
} else {
// Poll the channel and block until an event is available.
select {
case <-fidlCtx.Done():
return nil, fmt.Errorf("cancelled: %w", fidlCtx.Err())
case event := <-w.eventsChan:
events = append(events, event)
}
}
}
return events, nil
}
// watch services a single call to Watch. This function returns an error if
// there was already a pending call to Watch or if it was canceled while waiting
// for available events.
func (w *routesWatcherImplInner) watch(fidlCtx fidl.Context) ([]eventUnion, error) {
{
w.mu.Lock()
if w.mu.isHanging {
w.cancel()
w.mu.Unlock()
return nil, fmt.Errorf("Watch called while a prior call is still pending")
}
w.mu.isHanging = true
w.mu.Unlock()
}
// Fetch Events, blocking if none are available.
events, err := w.pullEvents(fidlCtx)
if err != nil {
w.cancel()
}
{
w.mu.Lock()
w.mu.isHanging = false
w.mu.Unlock()
}
return events, err
}
// isRouteInterrupt implements routeInterrupt.
func (*routesWatcherImplInner) isRouteInterrupt() {}
// routesWatcherV4Impl and routesWatcherV6Impl wrap routesWatcherImplInner to
// implement WatcherV4 and WatcherV6, respectively. Note that this layer of
// indirection is necessary because both WatcherV4 and WatcherV6 expose a Watch
// method with an identical signature. Trying to to implement the protocols
// directly leads to conflicting implementations (e.g. Watch func defined
// multiple times).
var _ fnetRoutes.WatcherV4WithCtx = (*routesWatcherV4Impl)(nil)
type routesWatcherV4Impl struct {
inner routesWatcherImplInner
}
var _ fnetRoutes.WatcherV6WithCtx = (*routesWatcherV6Impl)(nil)
type routesWatcherV6Impl struct {
inner routesWatcherImplInner
}
// Watch implements fuchsia.net.routes/WatcherV4.Watch.
func (w4 *routesWatcherV4Impl) Watch(ctx fidl.Context) ([]fnetRoutes.EventV4, error) {
events, err := w4.inner.watch(ctx)
if err != nil {
return nil, err
}
eventsV4 := make([]fnetRoutes.EventV4, 0, len(events))
for _, event := range events {
switch event.version {
case routetypes.IPv4:
eventsV4 = append(eventsV4, event.ipv4_event)
case routetypes.IPv6:
// Unreachable because of the filter installed by GetWatcherV4.
panic(fmt.Sprintf(
"Internal Error. %s received an IPv6 event: %s",
watcherV4ProtocolName,
intoLogString(event),
))
default:
panic(fmt.Sprintf("Event with invalid IP protocol :%d", event.version))
}
}
return eventsV4, nil
}
// Watch implements fuchsia.net.routes/WatcherV6.Watch.
func (w6 *routesWatcherV6Impl) Watch(ctx fidl.Context) ([]fnetRoutes.EventV6, error) {
events, err := w6.inner.watch(ctx)
if err != nil {
return nil, err
}
eventsV6 := make([]fnetRoutes.EventV6, 0, len(events))
for _, event := range events {
switch event.version {
case routetypes.IPv4:
// Unreachable because of the filter installed by GetWatcherV4.
panic(fmt.Sprintf(
"Internal Error. %s received an IPv4 event: %s",
watcherV6ProtocolName,
intoLogString(event),
))
case routetypes.IPv6:
eventsV6 = append(eventsV6, event.ipv6_event)
default:
panic(fmt.Sprintf("Event with invalid IP protocol :%d", event.version))
}
}
return eventsV6, nil
}
// fidlRoutesWatcherMetrics defines metrics for the
// fuchsia.net.routes Watcher protocols.
type fidlRoutesWatcherMetrics struct {
count_v4 atomic.Int64
count_v6 atomic.Int64
}
// eventUnion is a union type abstracting over IPv4 and IPv6 events.
// ipv4_event will be set iif version == IPv4 (and vice-versa for ipv6_event).
type eventUnion struct {
version routetypes.IpProtoTag
ipv4_event fnetRoutes.EventV4
ipv6_event fnetRoutes.EventV6
}
// maxPendingEventsPerClient is the maximum number of events queued for a single
// Watcher client. The value should be large enough to give clients some leeway
// when processing entries, but small enough to not waste memory. Clients are
// responsible for pulling events from the queue, thus it may back up.
const maxPendingEventsPerClient = 5 * fnetRoutes.MaxEvents
// maxPendingInterrupts is the maximum number of interrupts that have not yet
// been handled by the routesWatcherEventLoop. The value is somewhat arbitrary,
// because the changes are "drained" by the routesWatcherEventLoop immediately;
// however, larger values may reduce contention between goroutines.
const maxPendingInterrupts = 100
// routeInterrupt is a marker interface used to improve type safety in
// routesWatcherEventLoop.
type routeInterrupt interface {
isRouteInterrupt()
}
// routingTableChanged wraps routes.RoutingTableChanged so that it can implement
// routeEvent.
type routingTableChange struct {
routes.RoutingTableChange
}
// isRouteInterrupt implements routeInterrupt.
func (*routingTableChange) isRouteInterrupt() {}
// routesWatcherEventLoop is the main event loop servicing clients of the
// fuchsia.net.routes Watcher protocols.
func routesWatcherEventLoop(
ctx context.Context,
interruptChan chan routeInterrupt,
metrics *fidlRoutesWatcherMetrics,
) {
// Keep track of the current routing table state as changes are received.
// This allows us to push existing events to new watcher clients without
// having to call into the system Routing table, which may introduce race
// conditions.
currentRoutes := make(map[installedRouteComparisonKey]struct{})
// Keep track of the active Watcher implementations
currentWatchers := make(map[*routesWatcherImplInner]struct{})
for {
select {
case <-ctx.Done():
_ = syslog.WarnTf(routesFidlName, "stopping routes watcher event loop")
// Wait for all watchers to close.
for len(currentWatchers) > 0 {
switch interrupt := (<-interruptChan).(type) {
case *routesWatcherImplInner:
delete(currentWatchers, interrupt)
case *routingTableChange, *getWatcherV4Request, *getWatcherV6Request:
// Ignore all other interrupts when canceled.
default:
panic(fmt.Sprintf("unknown interrupt: %T", interrupt))
}
}
return
case interrupt := <-interruptChan:
switch interrupt := interrupt.(type) {
case *routingTableChange:
route := fidlconv.ToInstalledRoute(interrupt.Route)
key, err := toInstalledRouteComparisonKey(route)
if err != nil {
panic(fmt.Sprintf("observed error while attempting to compute comparison key for %#v: %s", route, err))
}
var eventType eventTag
// Updates routes based on the received change.
switch interrupt.Change {
case routetypes.RouteAdded:
if _, present := currentRoutes[key]; present {
panic(fmt.Sprintf(
"received duplicate add event for route: %+v",
interrupt.Route,
))
}
currentRoutes[key] = struct{}{}
eventType = addedEvent
case routetypes.RouteRemoved:
if _, present := currentRoutes[key]; !present {
panic(fmt.Sprintf(
"received remove event for non-existent route: %+v",
interrupt.Route,
))
}
delete(currentRoutes, key)
eventType = removedEvent
default:
panic(fmt.Sprintf(
"observed unexpected routing table change :%+v",
interrupt.Route,
))
}
// Notify the watchers of change
event := toEvent(eventType, route)
for watcherImpl := range currentWatchers {
watcherImpl.pushEvent(event)
}
case *getWatcherV4Request, *getWatcherV6Request:
// NB: because we're using a case-list, Golang will keep
// interrupt as an routeInterrupt, instead of rebinding it to a
// more specific type.
var watcher routesGetWatcherRequest
var ok bool
if watcher, ok = interrupt.(routesGetWatcherRequest); !ok {
panic(fmt.Sprintf(
"interrupt was impossibly not a routesGetWatcherRequest: %T",
interrupt,
))
}
// Serve the new watcher client.
eventsChan := make(chan eventUnion, maxPendingEventsPerClient)
watcherCtx, cancel := context.WithCancel(ctx)
impl := watcher.serve(
watcherCtx, cancel, currentRoutes, eventsChan, interruptChan, metrics,
)
currentWatchers[impl] = struct{}{}
case *routesWatcherImplInner:
delete(currentWatchers, interrupt)
default:
panic(fmt.Sprintf("unknown interrupt: %T", interrupt))
}
}
}
}
type eventTag uint32
const (
_ eventTag = iota
existingEvent
addedEvent
removedEvent
)
// toEvent converts the given route into an event based on the provided
// eventTag.
func toEvent(eventType eventTag, route fidlconv.InstalledRoute) eventUnion {
switch route.Version {
case routetypes.IPv4:
var event fnetRoutes.EventV4
switch eventType {
case existingEvent:
event.SetExisting(route.V4)
case addedEvent:
event.SetAdded(route.V4)
case removedEvent:
event.SetRemoved(route.V4)
default:
panic(fmt.Sprintf("invalid eventTag: %d", eventType))
}
return eventUnion{
version: routetypes.IPv4,
ipv4_event: event,
}
case routetypes.IPv6:
var event fnetRoutes.EventV6
switch eventType {
case existingEvent:
event.SetExisting(route.V6)
case addedEvent:
event.SetAdded(route.V6)
case removedEvent:
event.SetRemoved(route.V6)
default:
panic(fmt.Sprintf("invalid eventTag: %d", eventType))
}
return eventUnion{
version: routetypes.IPv6,
ipv6_event: event,
}
default:
panic(fmt.Sprintf("Route with invalid IP protocol :%d", route.Version))
}
}
// intoLogString converts the given eventUnion into a string suitable for logs.
// It's important not to log the FIDL type directly, as it's address format
// prevents PII redaction.
func intoLogString(e eventUnion) string {
fmtInstalledRouteV4 := func(r fnetRoutes.InstalledRouteV4) string {
dst := fidlconv.ToTCPIPSubnet(net.Subnet{
Addr: net.IpAddressWithIpv4(r.Route.Destination.Addr),
PrefixLen: r.Route.Destination.PrefixLen,
})
return fmt.Sprintf(
"InstalledRoute: %T{ EffectiveProperties: %#v, Route: %T{ Destination: %s, %s, Properties: %#v } }",
r,
r.EffectiveProperties,
r.Route,
dst,
func() string {
return fmt.Sprintf(
"Action: %T{ I_routeActionV4Tag: %d, Forward: %s }",
r.Route.Action,
r.Route.Action.I_routeActionV4Tag,
func() string {
switch r.Route.Action.I_routeActionV4Tag {
case fnetRoutes.RouteActionV4Forward:
return fmt.Sprintf(
"%T{ OutboundInterface: %d, NextHop: %s } }",
r.Route.Action,
r.Route.Action.I_routeActionV4Tag,
func() string {
if r.Route.Action.Forward.NextHop == nil {
return "nil"
} else {
return tcpip.AddrFrom4Slice(
r.Route.Action.Forward.NextHop.Addr[:]).String()
}
}())
default:
return fmt.Sprintf("%#v", r.Route.Action.Forward)
}
}())
}(),
r.Route.Properties)
}
fmtInstalledRouteV6 := func(r fnetRoutes.InstalledRouteV6) string {
dst := fidlconv.ToTCPIPSubnet(net.Subnet{
Addr: net.IpAddressWithIpv6(r.Route.Destination.Addr),
PrefixLen: r.Route.Destination.PrefixLen,
})
return fmt.Sprintf(
"InstalledRoute: %T{ EffectiveProperties: %#v, Route: %T{ Destination: %s, %s, Properties: %#v } }",
r,
r.EffectiveProperties,
r.Route,
dst,
func() string {
return fmt.Sprintf(
"Action: %T{ I_routeActionV6Tag: %d, Forward: %s }",
r.Route.Action,
r.Route.Action.I_routeActionV6Tag,
func() string {
switch r.Route.Action.I_routeActionV6Tag {
case fnetRoutes.RouteActionV6Forward:
return fmt.Sprintf(
"%T{ OutboundInterface: %d, NextHop: %s } }",
r.Route.Action,
r.Route.Action.I_routeActionV6Tag,
func() string {
if r.Route.Action.Forward.NextHop == nil {
return "nil"
} else {
return tcpip.AddrFrom16Slice(
r.Route.Action.Forward.NextHop.Addr[:]).String()
}
}())
default:
return fmt.Sprintf("%#v", r.Route.Action.Forward)
}
}())
}(),
r.Route.Properties)
}
return fmt.Sprintf(
"%T{ version: %d ipv4_event: %s, ipv6_event: %s}",
e,
e.version,
func() string {
switch e.version {
case routetypes.IPv4:
return fmt.Sprintf("%T{ I_eventV4Tag: %d, %s }",
e.ipv4_event, e.ipv4_event.I_eventV4Tag, func() string {
switch e.ipv4_event.I_eventV4Tag {
case fnetRoutes.EventV4Idle:
return "Idle: {}{}"
case fnetRoutes.EventV4Existing:
return fmt.Sprintf("Existing: %s",
fmtInstalledRouteV4(e.ipv4_event.Existing))
case fnetRoutes.EventV4Added:
return fmt.Sprintf("Added: %s",
fmtInstalledRouteV4(e.ipv4_event.Added))
case fnetRoutes.EventV4Removed:
return fmt.Sprintf("Removed: %s",
fmtInstalledRouteV4(e.ipv4_event.Removed))
default:
return "Unknown"
}
}())
default:
return "<unset>"
}
}(),
func() string {
switch e.version {
case routetypes.IPv6:
return fmt.Sprintf("%T{ I_eventV6Tag: %d, %s }",
e.ipv6_event, e.ipv6_event.I_eventV6Tag, func() string {
switch e.ipv6_event.I_eventV6Tag {
case fnetRoutes.EventV6Idle:
return "Idle: {}{}"
case fnetRoutes.EventV6Existing:
return fmt.Sprintf("Existing: %s",
fmtInstalledRouteV6(e.ipv6_event.Existing))
case fnetRoutes.EventV6Added:
return fmt.Sprintf("Added: %s",
fmtInstalledRouteV6(e.ipv6_event.Added))
case fnetRoutes.EventV6Removed:
return fmt.Sprintf("Removed: %s",
fmtInstalledRouteV6(e.ipv6_event.Removed))
default:
return "Unknown"
}
}())
default:
return "<unset>"
}
}(),
)
}
// installedRouteComparisonKey is an alternative to InstalledRoute whose boxed members
// are defined out-of-band at the top level. Two installedRouteComparisonKeys can be
// checked for equality, whereas two InstalledRoutes cannot (since their boxed
// members are behind a pointer, leading to address comparisons, not value
// comparisons).
type installedRouteComparisonKey struct {
fidlconv.RouteComparisonKey
Version routetypes.IpProtoTag
EffectiveRoutePropertiesPresent bool
EffectiveRouteProperties fnetRoutes.EffectiveRouteProperties
}
// toInstalledRouteComparisonKey converts an InstalledRoute to an installedRouteComparisonKey.
func toInstalledRouteComparisonKey(r fidlconv.InstalledRoute) (installedRouteComparisonKey, error) {
key, err := r.ToRouteComparisonKey()
if err != nil {
return installedRouteComparisonKey{}, err
}
present, props := func() (bool, fnetRoutes.EffectiveRouteProperties) {
switch r.Version {
case routetypes.IPv4:
return r.V4.EffectivePropertiesPresent, r.V4.EffectiveProperties
case routetypes.IPv6:
return r.V6.EffectivePropertiesPresent, r.V6.EffectiveProperties
default:
panic(fmt.Sprintf("unknown IP Version tag %d", r.Version))
}
}()
return installedRouteComparisonKey{
RouteComparisonKey: key,
Version: r.Version,
EffectiveRoutePropertiesPresent: present,
EffectiveRouteProperties: props,
}, nil
}
// fromInstalledRouteComparisonKey converts an installedRouteComparisonKey to an InstalledRoute.
func fromInstalledRouteComparisonKey(r installedRouteComparisonKey) (fidlconv.InstalledRoute, error) {
var result fidlconv.InstalledRoute
result.Version = r.Version
switch r.Version {
case routetypes.IPv4:
route, err := fidlconv.ToFidlRouteV4(r.V4)
if err != nil {
return result, err
}
result.V4.SetRoute(route)
result.V4.EffectivePropertiesPresent = r.EffectiveRoutePropertiesPresent
result.V4.EffectiveProperties = r.EffectiveRouteProperties
case routetypes.IPv6:
route, err := fidlconv.ToFidlRouteV6(r.V6)
if err != nil {
return result, err
}
result.V6.SetRoute(route)
result.V6.EffectivePropertiesPresent = r.EffectiveRoutePropertiesPresent
result.V6.EffectiveProperties = r.EffectiveRouteProperties
default:
panic(fmt.Sprintf("unknown IP Version tag %d", r.Version))
}
return result, nil
}