blob: 6cbb95490f65b25d9daaf5ef9ede17b49f489663 [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.
// +build !build_with_native_toolchain
package netstack
import (
"context"
"errors"
"fmt"
"strings"
"syscall/zx"
"syscall/zx/fidl"
"time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/lib/component"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/net"
"fidl/fuchsia/net/neighbor"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
nudTag = "NUD"
)
type nudDispatcher struct {
ns *Netstack
}
var _ stack.NUDDispatcher = (*nudDispatcher)(nil)
func (d *nudDispatcher) log(verb string, nicID tcpip.NICID, entry stack.NeighborEntry) {
family := func() string {
switch l := len(entry.Addr); l {
case header.IPv4AddressSize:
return "v4"
case header.IPv6AddressSize:
return "v6"
default:
return fmt.Sprintf("l=%d", l)
}
}()
flags := func() string {
var defaultGateway, onLink bool
for _, route := range d.ns.stack.GetRouteTable() {
if route.NIC != nicID {
continue
}
if len(route.Destination.ID()) != len(entry.Addr) {
continue
}
if route.Destination.Prefix() == 0 {
if route.Gateway == entry.Addr {
defaultGateway = true
}
} else if route.Destination.Contains(entry.Addr) {
if len(route.Gateway) == 0 {
onLink = true
}
}
}
var b strings.Builder
if defaultGateway {
b.WriteByte('G')
}
if onLink {
b.WriteByte('L')
}
if b.Len() != 0 {
return b.String()
}
return "U"
}()
// TODO(https://fxbug.dev/62788): Change log level to Debug once the neighbor table
// is able to be inspected.
_ = syslog.InfoTf(nudTag, "%s %s (%s|%s) NIC=%d LinkAddress=%s %s", verb, entry.Addr, family, flags, nicID, entry.LinkAddr, entry.State)
}
// OnNeighborAdded implements stack.NUDDispatcher.
func (d *nudDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry stack.NeighborEntry) {
d.log("ADD", nicID, entry)
}
// OnNeighborChanged implements stack.NUDDispatcher.
func (d *nudDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry stack.NeighborEntry) {
d.log("MOD", nicID, entry)
}
// OnNeighborRemoved implements stack.NUDDispatcher.
func (d *nudDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry stack.NeighborEntry) {
d.log("DEL", nicID, entry)
}
type neighborImpl struct {
stack *stack.Stack
}
var _ neighbor.ViewWithCtx = (*neighborImpl)(nil)
func (n *neighborImpl) OpenEntryIterator(ctx fidl.Context, it neighbor.EntryIteratorWithCtxInterfaceRequest, _ neighbor.EntryIteratorOptions) error {
// TODO(https://fxbug.dev/59425): Watch for changes.
var items []neighbor.EntryIteratorItem
for nicID := range n.stack.NICInfo() {
for _, network := range []tcpip.NetworkProtocolNumber{header.IPv4ProtocolNumber, header.IPv6ProtocolNumber} {
neighbors, err := n.stack.Neighbors(nicID, network)
switch err.(type) {
case nil:
case *tcpip.ErrNotSupported:
// This NIC does not use a neighbor table.
continue
case *tcpip.ErrUnknownNICID:
// This NIC was removed since stack.NICInfo() was called.
continue
default:
_ = syslog.ErrorTf(neighbor.ViewName, "EntryIterator received unexpected error from Neighbors(%d): %s", nicID, err)
_ = it.Close()
return WrapTcpIpError(err)
}
for _, n := range neighbors {
if netEntry, ok := toNeighborEntry(nicID, n); ok {
items = append(items, neighbor.EntryIteratorItemWithExisting(netEntry))
}
}
}
}
// End the list with a special item to indicate the end of existing entries.
items = append(items, neighbor.EntryIteratorItemWithIdle(neighbor.IdleEvent{}))
stub := neighbor.EntryIteratorWithCtxStub{
Impl: &neighborEntryIterator{
items: items,
},
}
go component.ServeExclusive(context.Background(), &stub, it.Channel, func(err error) {
_ = syslog.WarnTf(neighbor.ViewName, "EntryIterator: %s", err)
})
return nil
}
func (n *neighborImpl) GetUnreachabilityConfig(_ fidl.Context, interfaceID uint64, ipVersion net.IpVersion) (neighbor.ViewGetUnreachabilityConfigResult, error) {
netProto, ok := fidlconv.ToTCPNetProto(ipVersion)
if !ok {
return neighbor.ViewGetUnreachabilityConfigResultWithErr(int32(zx.ErrInvalidArgs)), nil
}
config, err := n.stack.NUDConfigurations(tcpip.NICID(interfaceID), netProto)
if err != nil {
return neighbor.ViewGetUnreachabilityConfigResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
var resp neighbor.UnreachabilityConfig
resp.SetBaseReachableTime(config.BaseReachableTime.Nanoseconds())
resp.SetLearnBaseReachableTime(config.LearnBaseReachableTime)
resp.SetMinRandomFactor(config.MinRandomFactor)
resp.SetMaxRandomFactor(config.MaxRandomFactor)
resp.SetRetransmitTimer(config.RetransmitTimer.Nanoseconds())
resp.SetLearnRetransmitTimer(config.LearnRetransmitTimer)
resp.SetDelayFirstProbeTime(config.DelayFirstProbeTime.Nanoseconds())
resp.SetMaxMulticastProbes(config.MaxMulticastProbes)
resp.SetMaxUnicastProbes(config.MaxUnicastProbes)
resp.SetMaxAnycastDelayTime(config.MaxAnycastDelayTime.Nanoseconds())
resp.SetMaxReachabilityConfirmations(config.MaxReachabilityConfirmations)
return neighbor.ViewGetUnreachabilityConfigResultWithResponse(neighbor.ViewGetUnreachabilityConfigResponse{
Config: resp,
}), nil
}
var _ neighbor.ControllerWithCtx = (*neighborImpl)(nil)
func (n *neighborImpl) AddEntry(_ fidl.Context, interfaceID uint64, neighborIP net.IpAddress, mac net.MacAddress) (neighbor.ControllerAddEntryResult, error) {
address, network := fidlconv.ToTCPIPAddressAndProtocolNumber(neighborIP)
if err := n.stack.AddStaticNeighbor(tcpip.NICID(interfaceID), network, address, fidlconv.ToTCPIPLinkAddress(mac)); err != nil {
return neighbor.ControllerAddEntryResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
return neighbor.ControllerAddEntryResultWithResponse(neighbor.ControllerAddEntryResponse{}), nil
}
func (n *neighborImpl) RemoveEntry(_ fidl.Context, interfaceID uint64, neighborIP net.IpAddress) (neighbor.ControllerRemoveEntryResult, error) {
address, network := fidlconv.ToTCPIPAddressAndProtocolNumber(neighborIP)
if err := n.stack.RemoveNeighbor(tcpip.NICID(interfaceID), network, address); err != nil {
var zxErr zx.Status
switch err.(type) {
case *tcpip.ErrBadAddress:
zxErr = zx.ErrNotFound
default:
zxErr = WrapTcpIpError(err).ToZxStatus()
}
return neighbor.ControllerRemoveEntryResultWithErr(int32(zxErr)), nil
}
return neighbor.ControllerRemoveEntryResultWithResponse(neighbor.ControllerRemoveEntryResponse{}), nil
}
func (n *neighborImpl) ClearEntries(_ fidl.Context, interfaceID uint64, ipVersion net.IpVersion) (neighbor.ControllerClearEntriesResult, error) {
netProto, ok := fidlconv.ToTCPNetProto(ipVersion)
if !ok {
return neighbor.ControllerClearEntriesResultWithErr(int32(zx.ErrInvalidArgs)), nil
}
if err := n.stack.ClearNeighbors(tcpip.NICID(interfaceID), netProto); err != nil {
return neighbor.ControllerClearEntriesResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
return neighbor.ControllerClearEntriesResultWithResponse(neighbor.ControllerClearEntriesResponse{}), nil
}
func (n *neighborImpl) UpdateUnreachabilityConfig(_ fidl.Context, interfaceID uint64, ipVersion net.IpVersion, config neighbor.UnreachabilityConfig) (neighbor.ControllerUpdateUnreachabilityConfigResult, error) {
if !n.stack.HasNIC(tcpip.NICID(interfaceID)) {
return neighbor.ControllerUpdateUnreachabilityConfigResultWithErr(int32(zx.ErrNotFound)), nil
}
netProto, ok := fidlconv.ToTCPNetProto(ipVersion)
if !ok {
return neighbor.ControllerUpdateUnreachabilityConfigResultWithErr(int32(zx.ErrInvalidArgs)), nil
}
currentConfig, err := n.stack.NUDConfigurations(tcpip.NICID(interfaceID), netProto)
if err != nil {
return neighbor.ControllerUpdateUnreachabilityConfigResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
invalidArgsResult := neighbor.ControllerUpdateUnreachabilityConfigResultWithErr(int32(zx.ErrInvalidArgs))
// See fuchsia.net.neighbor/UnreachabilityConfig for the list of constraints.
if config.HasBaseReachableTime() {
if v := config.GetBaseReachableTime(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `base_reachable_time` %d: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.BaseReachableTime = time.Duration(config.GetBaseReachableTime())
}
if config.HasLearnBaseReachableTime() {
currentConfig.LearnBaseReachableTime = config.GetLearnBaseReachableTime()
}
if config.HasMinRandomFactor() {
if v := config.GetMinRandomFactor(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `min_random_factor` %f: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.MinRandomFactor = config.GetMinRandomFactor()
}
if config.HasMaxRandomFactor() {
if v := config.GetMaxRandomFactor(); v < currentConfig.MinRandomFactor {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `max_random_factor` %f: must be >= `min_random_factor` %f", v, currentConfig.MinRandomFactor)
return invalidArgsResult, nil
}
currentConfig.MaxRandomFactor = config.GetMaxRandomFactor()
}
if config.HasRetransmitTimer() {
if v := config.GetRetransmitTimer(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `retransmit_timer` %d: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.RetransmitTimer = time.Duration(config.GetRetransmitTimer())
}
if config.HasLearnRetransmitTimer() {
currentConfig.LearnRetransmitTimer = config.GetLearnRetransmitTimer()
}
if config.HasDelayFirstProbeTime() {
if v := config.GetDelayFirstProbeTime(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `delay_first_probe_time` %d: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.DelayFirstProbeTime = time.Duration(config.GetDelayFirstProbeTime())
}
if config.HasMaxMulticastProbes() {
if v := config.GetMaxMulticastProbes(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `max_multicast_probes` %d: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.MaxMulticastProbes = config.GetMaxMulticastProbes()
}
if config.HasMaxUnicastProbes() {
if v := config.GetMaxUnicastProbes(); v <= 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `max_unicast_probes` %d: must be > 0", v)
return invalidArgsResult, nil
}
currentConfig.MaxUnicastProbes = config.GetMaxUnicastProbes()
}
if config.HasMaxAnycastDelayTime() {
if v := config.GetMaxAnycastDelayTime(); v < 0 {
_ = syslog.WarnTf(neighbor.ControllerName, "UpdateUnreachabilityConfig: invalid `max_anycast_delay_time` %d: must be >= 0", v)
return invalidArgsResult, nil
}
currentConfig.MaxAnycastDelayTime = time.Duration(config.GetMaxAnycastDelayTime())
}
if config.HasMaxReachabilityConfirmations() {
currentConfig.MaxReachabilityConfirmations = config.GetMaxReachabilityConfirmations()
}
if err := n.stack.SetNUDConfigurations(tcpip.NICID(interfaceID), netProto, currentConfig); err != nil {
return neighbor.ControllerUpdateUnreachabilityConfigResultWithErr(int32(WrapTcpIpError(err).ToZxStatus())), nil
}
return neighbor.ControllerUpdateUnreachabilityConfigResultWithResponse(neighbor.ControllerUpdateUnreachabilityConfigResponse{}), nil
}
// neighborEntryIterator queues events received from the neighbor table for
// consumption by a FIDL client.
type neighborEntryIterator struct {
// items contains neighbor entry notifications waiting for client consumption.
items []neighbor.EntryIteratorItem
}
var _ neighbor.EntryIteratorWithCtx = (*neighborEntryIterator)(nil)
// GetNext implements neighbor.EntryIteratorWithCtx.GetNext.
func (it *neighborEntryIterator) GetNext(fidl.Context) ([]neighbor.EntryIteratorItem, error) {
if len(it.items) == 0 {
// TODO(https://fxbug.dev/59425): Watch for changes instead of closing the
// connection. This was deferred to unblock listing entries.
return nil, errors.New("watching for changes not supported")
}
items := it.items
if uint64(len(it.items)) > neighbor.MaxItemBatchSize {
// There are too many items to send; only send the max amount and leave the
// rest for subsequent calls.
items = items[:neighbor.MaxItemBatchSize]
}
it.items = it.items[len(items):]
// Avoid memory leak on always-appended slice.
if len(it.items) == 0 {
it.items = nil
}
return items, nil
}
// toNeighborEntry converts a stack.NeighborEntry to a
// fuchsia.net.neighbor/Entry. Returns the converted entry and true if the
// conversion was successful, false otherwise.
func toNeighborEntry(nicID tcpip.NICID, n stack.NeighborEntry) (neighbor.Entry, bool) {
e := neighbor.Entry{}
e.SetInterface(uint64(nicID))
e.SetUpdatedAt(n.UpdatedAtNanos)
if len(n.Addr) != 0 {
e.SetNeighbor(fidlconv.ToNetIpAddress(n.Addr))
}
if len(n.LinkAddr) != 0 {
e.SetMac(fidlconv.ToNetMacAddress(n.LinkAddr))
}
switch n.State {
case stack.Unknown:
// Unknown is an internal state used by the netstack to represent a newly
// created or deleted entry. Clients do not need to be concerned with this
// in-between state; all transitions into and out of the Unknown state
// trigger an event.
return e, false
case stack.Incomplete:
e.SetState(neighbor.EntryStateIncomplete)
case stack.Reachable:
e.SetState(neighbor.EntryStateReachable)
case stack.Stale:
e.SetState(neighbor.EntryStateStale)
case stack.Delay:
e.SetState(neighbor.EntryStateDelay)
case stack.Probe:
e.SetState(neighbor.EntryStateProbe)
case stack.Static:
e.SetState(neighbor.EntryStateStatic)
case stack.Unreachable:
e.SetState(neighbor.EntryStateUnreachable)
default:
panic(fmt.Sprintf("invalid NeighborState = %d: %#v", n.State, n))
}
return e, true
}