blob: 225478141d583424756504acfd7534caa4d1487a [file] [log] [blame]
// Copyright 2021 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"
"errors"
"fmt"
"syscall/zx"
"syscall/zx/fidl"
"time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/netdevice"
"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/hardware/network"
"fidl/fuchsia/net"
"fidl/fuchsia/net/interfaces"
"fidl/fuchsia/net/interfaces/admin"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
addressStateProviderName = "fuchsia.net.interfaces.admin/AddressStateProvider"
controlName = "fuchsia.net.interfaces.admin/Control"
deviceControlName = "fuchsia.net.interfaces.admin/DeviceControl"
)
var _ stack.AddressDispatcher = (*addressDispatcher)(nil)
type addressDispatcher struct {
watcherDisp watcherAddressDispatcher
mu struct {
sync.Mutex
aspImpl *adminAddressStateProviderImpl
}
}
func (ad *addressDispatcher) OnChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) {
ad.watcherDisp.OnChanged(lifetimes, state)
ad.mu.Lock()
if ad.mu.aspImpl != nil {
ad.mu.aspImpl.OnChanged(lifetimes, state)
}
ad.mu.Unlock()
}
func (ad *addressDispatcher) OnRemoved(reason stack.AddressRemovalReason) {
ad.mu.Lock()
if ad.mu.aspImpl != nil {
ad.mu.aspImpl.OnRemoved(reason)
}
ad.mu.Unlock()
ad.watcherDisp.OnRemoved(reason)
}
var _ admin.AddressStateProviderWithCtx = (*adminAddressStateProviderImpl)(nil)
var _ stack.AddressDispatcher = (*adminAddressStateProviderImpl)(nil)
type adminAddressStateProviderImpl struct {
ns *Netstack
nicid tcpip.NICID
cancelServe context.CancelFunc
ready chan struct{}
protocolAddr tcpip.ProtocolAddress
mu struct {
sync.Mutex
eventProxy admin.AddressStateProviderEventProxy
isHanging bool
// NB: state is the zero value while the address has been added but the
// initial assignment state is unknown.
state interfaces.AddressAssignmentState
// NB: lastObserved is the zero value iff the client has yet to observe the
// state for the first time.
lastObserved interfaces.AddressAssignmentState
// detached is set to true if Detach has been called on the channel, and will
// result in the address not being removed when the client closes its end of
// the channel.
detached bool
// removedReason is set to the reason why the address was removed. When this
// is set, the address does not need to be removed when this protocol
// terminates.
removedReason stack.AddressRemovalReason
// Indicates if the OnAddressAdded event was sent.
sentAddedEvent bool
}
// The RouteSetId used to associate this address to any subnet route added
// as a result of the add_subnet_route AddressParameters field.
subnetRouteSetId routetypes.RouteSetId
// The add_subnet_route AddressParameters field from when this address was
// added to the stack.
addSubnetRoute bool
}
func (pi *adminAddressStateProviderImpl) UpdateAddressProperties(_ fidl.Context, properties admin.AddressProperties) error {
lifetimes := propertiesToLifetimes(properties)
switch err := pi.ns.stack.SetAddressLifetimes(
pi.nicid,
pi.protocolAddr.AddressWithPrefix.Address,
lifetimes,
); err.(type) {
case nil:
case *tcpip.ErrUnknownNICID, *tcpip.ErrBadLocalAddress:
// TODO(https://fxbug.dev/42176338): Upgrade to panic once we're guaranteed that we get here iff the address still exists.
_ = syslog.WarnTf(addressStateProviderName, "SetAddressLifetimes(%d, %s, %#v) failed: %s",
pi.nicid, pi.protocolAddr.AddressWithPrefix.Address, lifetimes, err)
default:
panic(fmt.Sprintf("SetAddressLifetimes(%d, %s, %#v) failed with unexpected error: %s",
pi.nicid, pi.protocolAddr.AddressWithPrefix.Address, lifetimes, err))
}
return nil
}
func (pi *adminAddressStateProviderImpl) Detach(fidl.Context) error {
pi.mu.Lock()
defer pi.mu.Unlock()
pi.mu.detached = true
return nil
}
func (pi *adminAddressStateProviderImpl) WatchAddressAssignmentState(ctx fidl.Context) (interfaces.AddressAssignmentState, error) {
pi.mu.Lock()
defer pi.mu.Unlock()
if pi.mu.isHanging {
pi.cancelServe()
return 0, errors.New("not allowed to call WatchAddressAssignmentState when a call is already in progress")
}
for {
if pi.mu.lastObserved != pi.mu.state {
state := pi.mu.state
pi.mu.lastObserved = state
syslog.DebugTf(addressStateProviderName, "NIC=%d address %+v observed state: %s", pi.nicid, pi.protocolAddr, state)
return state, nil
}
pi.mu.isHanging = true
pi.mu.Unlock()
var err error
select {
case <-pi.ready:
case <-ctx.Done():
err = fmt.Errorf("cancelled: %w", ctx.Err())
}
pi.mu.Lock()
pi.mu.isHanging = false
if err != nil {
return 0, err
}
}
}
var _ admin.ControlWithCtx = (*adminControlImpl)(nil)
type adminControlImpl struct {
ns *Netstack
nicid tcpip.NICID
cancelServe context.CancelFunc
syncRemoval bool
doneChannel chan zx.Channel
// TODO(https://fxbug.dev/42169142): encode owned, strong, and weak refs once
// cloning Control is allowed.
isStrongRef bool
}
func (ci *adminControlImpl) getNICContext() *ifState {
nicInfo, ok := ci.ns.stack.NICInfo()[ci.nicid]
if !ok {
// All serving control channels must be canceled before removing NICs from
// the stack, this is a violation of that invariant.
panic(fmt.Sprintf("NIC %d not found", ci.nicid))
}
return nicInfo.Context.(*ifState)
}
func (ci *adminControlImpl) Enable(fidl.Context) (admin.ControlEnableResult, error) {
wasEnabled, err := ci.getNICContext().setState(true /* enabled */)
if err != nil {
// The only known error path that causes this failure is failure from the
// device layers, which all mean we're possible racing with shutdown.
_ = syslog.Errorf("ifs.Up() failed (NIC %d): %s", ci.nicid, err)
return admin.ControlEnableResult{}, err
}
return admin.ControlEnableResultWithResponse(
admin.ControlEnableResponse{
DidEnable: !wasEnabled,
}), nil
}
func (ci *adminControlImpl) Disable(fidl.Context) (admin.ControlDisableResult, error) {
wasEnabled, err := ci.getNICContext().setState(false /* enabled */)
if err != nil {
// The only known error path that causes this failure is failure from the
// device layers, which all mean we're possible racing with shutdown.
_ = syslog.Errorf("ifs.Down() failed (NIC %d): %s", ci.nicid, err)
return admin.ControlDisableResult{}, err
}
return admin.ControlDisableResultWithResponse(
admin.ControlDisableResponse{
DidDisable: wasEnabled,
}), nil
}
func (ci *adminControlImpl) Detach(fidl.Context) error {
// Make it a weak ref but don't decrease the reference count. If this was a
// strong ref, the interface will leak.
//
// TODO(https://fxbug.dev/42169142): Detach should only be allowed on OWNED refs
// once we allow cloning Control.
ci.isStrongRef = false
return nil
}
func (ci *adminControlImpl) isLoopback() bool {
nicInfo, ok := ci.ns.stack.NICInfo()[ci.nicid]
if !ok {
panic(fmt.Sprintf("Remove on NIC %d not present", ci.nicid))
}
return nicInfo.Flags.Loopback
}
func (ci *adminControlImpl) Remove(fidl.Context) (admin.ControlRemoveResult, error) {
if ci.isLoopback() {
return admin.ControlRemoveResultWithErr(admin.ControlRemoveErrorNotAllowed), nil
}
ci.syncRemoval = true
ci.cancelServe()
return admin.ControlRemoveResultWithResponse(admin.ControlRemoveResponse{}), nil
}
func (ci *adminControlImpl) GetAuthorizationForInterface(fidl.Context) (admin.GrantForInterfaceAuthorization, error) {
nicInfo := ci.getNICContext()
token, err := nicInfo.authorizationToken.Duplicate(zx.RightTransfer | zx.RightDuplicate)
if err != nil {
return admin.GrantForInterfaceAuthorization{}, err
}
return admin.GrantForInterfaceAuthorization{InterfaceId: uint64(nicInfo.nicid), Token: token}, nil
}
func propertiesToLifetimes(properties admin.AddressProperties) stack.AddressLifetimes {
lifetimes := stack.AddressLifetimes{
ValidUntil: tcpip.MonotonicTimeInfinite(),
}
if properties.HasValidLifetimeEnd() {
lifetimes.ValidUntil = fidlconv.ToTCPIPMonotonicTime(zx.Time(properties.GetValidLifetimeEnd()))
}
if properties.HasPreferredLifetimeInfo() {
switch preferred := properties.GetPreferredLifetimeInfo(); preferred.Which() {
case interfaces.PreferredLifetimeInfoDeprecated:
lifetimes.Deprecated = true
case interfaces.PreferredLifetimeInfoPreferredUntil:
lifetimes.PreferredUntil = fidlconv.ToTCPIPMonotonicTime(zx.Time(preferred.PreferredUntil))
default:
panic(fmt.Sprintf("unknown preferred lifetime info tag: %+v", preferred))
}
} else {
lifetimes.PreferredUntil = tcpip.MonotonicTimeInfinite()
}
return lifetimes
}
func (pi *adminAddressStateProviderImpl) OnChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) {
_ = syslog.DebugTf(addressStateProviderName, "NIC=%d addr=%s changed lifetimes=%#v state=%s",
pi.nicid, pi.protocolAddr.AddressWithPrefix, lifetimes, state)
pi.mu.Lock()
defer pi.mu.Unlock()
pi.mu.state = fidlconv.ToAddressAssignmentState(state)
if pi.mu.lastObserved != pi.mu.state {
syslog.DebugTf(addressStateProviderName, "NIC=%d address %+v state changed from %s to %s", pi.nicid, pi.protocolAddr.AddressWithPrefix, pi.mu.lastObserved, pi.mu.state)
select {
case pi.ready <- struct{}{}:
default:
}
}
}
func (pi *adminAddressStateProviderImpl) Remove(fidl.Context) error {
_ = syslog.DebugTf(addressStateProviderName, "NICID=%d removing address %s from NIC %d due to explicit removal request", pi.nicid, pi.protocolAddr.AddressWithPrefix, pi.nicid)
pi.mu.Lock()
prevRemovedReason := pi.mu.removedReason
// If not already set, removedReason will be set when the address is removed
// below by the synchronous callback to pi.OnRemoved.
pi.mu.Unlock()
if prevRemovedReason != 0 {
return nil
}
nicInfo, ok := pi.ns.stack.NICInfo()[pi.nicid]
if !ok {
panic(fmt.Sprintf("NIC %d not found when removing %s", pi.nicid, pi.protocolAddr.AddressWithPrefix))
}
ifs := nicInfo.Context.(*ifState)
switch status := ifs.removeAddress(pi.protocolAddr); status {
case zx.ErrOk:
case zx.ErrNotFound:
// Normally, we'd expect that it's impossible to get NotFound while holding
// an AddressStateProvider, as the existence of the ASP itself should
// indicate that the address is present.
// However, we could be racing with fuchsia.net.interfaces.admin.Control/RemoveAddress
// (see adminControlImpl.RemoveAddress in this file).
_ = syslog.ErrorTf(addressStateProviderName, "tried to remove address %s that was not found on NIC %d", pi.protocolAddr.AddressWithPrefix, pi.nicid)
case zx.ErrBadState:
_ = syslog.WarnTf(addressStateProviderName, "NIC %d removed when trying to remove address %s due to explicit removal request: %s", pi.nicid, pi.protocolAddr.AddressWithPrefix, status)
default:
panic(fmt.Sprintf("NICID=%d unknown error trying to remove address %s upon explicit removal request: %s", pi.nicid, pi.protocolAddr.AddressWithPrefix, status))
}
return nil
}
func (pi *adminAddressStateProviderImpl) sendOnAddressRemovedEventAndCancelServeLocked() {
if err := pi.mu.eventProxy.OnAddressRemoved(fidlconv.ToAddressRemovalReason(pi.mu.removedReason)); err != nil {
var zxError *zx.Error
if !errors.As(err, &zxError) || (zxError.Status != zx.ErrPeerClosed && zxError.Status != zx.ErrBadHandle) {
_ = syslog.ErrorTf(addressStateProviderName, "NICID=%d failed to send OnAddressRemoved(%s) for %s: %s", pi.nicid, pi.mu.removedReason, pi.protocolAddr.AddressWithPrefix.Address, err)
}
}
pi.cancelServe()
}
func (pi *adminAddressStateProviderImpl) OnRemoved(reason stack.AddressRemovalReason) {
_ = syslog.DebugTf(addressStateProviderName, "NIC=%d addr=%s removed reason=%s", pi.nicid, pi.protocolAddr.AddressWithPrefix, reason)
pi.mu.Lock()
defer pi.mu.Unlock()
pi.mu.removedReason = reason
// If the OnAddressAdded event has not been sent yet, then that means the
// address was removed while fuchsia.net.interfaces.admin/Control.AddAddress
// operation is in-progress. This can happen when immediately after adding the
// address to the core (gVisor) netstack but before the OnAddressAdded event
// was sent (when no locks are held), DAD fails which triggers address
// removal.
if pi.mu.sentAddedEvent {
pi.sendOnAddressRemovedEventAndCancelServeLocked()
}
}
func (pi *adminAddressStateProviderImpl) cleanUpSubnetRoute() {
if pi.addSubnetRoute {
pi.ns.DelRouteSet(&pi.subnetRouteSetId)
}
}
func (ci *adminControlImpl) AddAddress(_ fidl.Context, subnet net.Subnet, parameters admin.AddressParameters, request admin.AddressStateProviderWithCtxInterfaceRequest) error {
protocolAddr := fidlconv.ToTCPIPProtocolAddress(subnet)
addr := protocolAddr.AddressWithPrefix.Address
ifs := ci.getNICContext()
ctx, cancel := context.WithCancel(context.Background())
impl := &adminAddressStateProviderImpl{
ns: ci.ns,
nicid: ci.nicid,
ready: make(chan struct{}, 1),
cancelServe: cancel,
protocolAddr: protocolAddr,
}
impl.mu.Lock()
impl.mu.eventProxy.Channel = request.Channel
impl.mu.Unlock()
addrDisp := &addressDispatcher{
watcherDisp: watcherAddressDispatcher{
nicid: ifs.nicid,
protocolAddr: protocolAddr,
ch: ifs.ns.interfaceEventChan,
},
}
addrDisp.mu.Lock()
addrDisp.mu.aspImpl = impl
addrDisp.mu.Unlock()
properties := stack.AddressProperties{
Temporary: parameters.GetTemporaryWithDefault(false),
Disp: addrDisp,
}
if parameters.HasInitialProperties() {
properties.Lifetimes = propertiesToLifetimes(parameters.GetInitialProperties())
}
var reason admin.AddressRemovalReason
if protocolAddr.AddressWithPrefix.PrefixLen > protocolAddr.AddressWithPrefix.Address.BitLen() {
reason = admin.AddressRemovalReasonInvalid
} else if ok, status := ifs.addAddress(protocolAddr, properties); !ok {
reason = status
}
if reason != 0 {
defer cancel()
impl.mu.Lock()
defer impl.mu.Unlock()
if err := impl.mu.eventProxy.OnAddressRemoved(reason); err != nil {
var zxError *zx.Error
if !errors.As(err, &zxError) || (zxError.Status != zx.ErrPeerClosed && zxError.Status != zx.ErrBadHandle) {
_ = syslog.WarnTf(controlName, "NICID=%d failed to send OnAddressRemoved(%s) for %s: %s", impl.nicid, reason, protocolAddr.AddressWithPrefix.Address, err)
}
}
if err := impl.mu.eventProxy.Close(); err != nil {
_ = syslog.WarnTf(controlName, "NICID=%d failed to close %s channel", impl.nicid, addressStateProviderName)
}
return nil
}
addSubnetRoute := parameters.GetAddSubnetRouteWithDefault(false)
impl.addSubnetRoute = addSubnetRoute
if addSubnetRoute {
// We treat every address as being associated with its own route set.
// This allows us to close the address's route set when the address is
// removed, guaranteeing we clean up the associated subnet route.
impl.ns.AddRoute(
addressWithPrefixRoute(ifs.nicid, protocolAddr.AddressWithPrefix),
nil, /* metric*/
false, /* dynamic */
false, /* replaceMatchingGvisorRoutes */
&impl.subnetRouteSetId,
)
}
impl.mu.Lock()
defer impl.mu.Unlock()
impl.mu.sentAddedEvent = true
if err := impl.mu.eventProxy.OnAddressAdded(); err != nil {
_ = syslog.ErrorTf(controlName, "NICID=%d failed to send OnAddressAdded() for %s - THIS MAY RESULT IN DROPPED ASP REQUESTS (https://fxbug.dev/42081560): %s", impl.nicid, protocolAddr.AddressWithPrefix.Address, err)
}
// If the address was removed before we sent the OnAddressAdded event, then
// that means we need to send the (pending) OnAddressRemoved event. See
// pi.OnRemoved for details.
if impl.mu.removedReason != 0 {
impl.sendOnAddressRemovedEventAndCancelServeLocked()
}
go func() {
defer cancel()
component.Serve(ctx, &admin.AddressStateProviderWithCtxStub{Impl: impl}, request.Channel, component.ServeOptions{
Concurrent: true,
OnError: func(err error) {
_ = syslog.WarnTf(addressStateProviderName, "NICID=%d address state provider for %s: %s", impl.nicid, addr, err)
},
})
impl.mu.Lock()
detached, removedReason := impl.mu.detached, impl.mu.removedReason
impl.mu.Unlock()
addrDisp.mu.Lock()
if detached {
addrDisp.mu.aspImpl = nil
}
addrDisp.mu.Unlock()
if !detached && removedReason == 0 {
_ = syslog.DebugTf(addressStateProviderName, "NICID=%d removing address %s from NIC %d due to protocol closure", impl.nicid, addr, ci.nicid)
switch status := ifs.removeAddress(impl.protocolAddr); status {
case zx.ErrOk, zx.ErrNotFound:
case zx.ErrBadState:
_ = syslog.WarnTf(addressStateProviderName, "NIC %d removed when trying to remove address %s upon channel closure: %s", ci.nicid, addr, status)
default:
panic(fmt.Sprintf("NICID=%d unknown error trying to remove address %s upon channel closure: %s", impl.nicid, addr, status))
}
}
if !detached {
impl.cleanUpSubnetRoute()
}
}()
return nil
}
func (ci *adminControlImpl) RemoveAddress(_ fidl.Context, address net.Subnet) (admin.ControlRemoveAddressResult, error) {
protocolAddr := fidlconv.ToTCPIPProtocolAddress(address)
nicInfo, ok := ci.ns.stack.NICInfo()[ci.nicid]
if !ok {
panic(fmt.Sprintf("NIC %d not found when removing %s", ci.nicid, protocolAddr.AddressWithPrefix))
}
ifs := nicInfo.Context.(*ifState)
// If the address the caller requested to remove was assigned through DHCP,
// just stop DHCP since that will result in the removal of the address.
ifs.dhcpLock <- struct{}{}
ifs.mu.Lock()
defer func() {
ifs.mu.Unlock()
<-ifs.dhcpLock
}()
// DHCP client is only available for Ethernet interfaces.
if ifs.mu.dhcp.Client != nil {
if info := ifs.mu.dhcp.Client.Info(); info.Assigned.Address == protocolAddr.AddressWithPrefix.Address {
ifs.setDHCPStatusLocked(nicInfo.Name, false)
return admin.ControlRemoveAddressResultWithResponse(admin.ControlRemoveAddressResponse{DidRemove: true}), nil
}
}
switch zxErr := ifs.removeAddress(protocolAddr); zxErr {
case zx.ErrOk:
return admin.ControlRemoveAddressResultWithResponse(admin.ControlRemoveAddressResponse{DidRemove: true}), nil
case zx.ErrNotFound:
return admin.ControlRemoveAddressResultWithResponse(admin.ControlRemoveAddressResponse{DidRemove: false}), nil
default:
panic(fmt.Sprintf("removeInterfaceAddress(%d, %v, false) = %s", ci.nicid, protocolAddr, zxErr))
}
}
func (ci *adminControlImpl) GetId(fidl.Context) (uint64, error) {
return uint64(ci.nicid), nil
}
// handleIPForwardingConfigurationResult handles the result of getting or
// setting IP forwarding or IP multicast forwarding configuration.
//
// Returns the result if the invoked function was successful. Otherwise, panics
// if an unexpected error occurred.
func handleIPForwardingConfigurationResult(result bool, err tcpip.Error, invokedFunction string) bool {
switch err.(type) {
case nil:
return result
case *tcpip.ErrUnknownNICID:
// Impossible as this Control would be invalid if the interface is not
// recognized.
panic(fmt.Sprintf("got UnknownNICID error when Control is still valid from %s = %s", invokedFunction, err))
default:
panic(fmt.Sprintf("%s: %s", invokedFunction, err))
}
}
// setIPForwardingLocked sets the IP forwarding configuration for the interface.
//
// The caller must hold the interface's write lock.
func (ci *adminControlImpl) setIPForwardingLocked(netProto tcpip.NetworkProtocolNumber, enabled bool) bool {
prevEnabled, err := ci.ns.stack.SetNICForwarding(tcpip.NICID(ci.nicid), netProto, enabled)
return handleIPForwardingConfigurationResult(prevEnabled, err, fmt.Sprintf("ci.ns.stack.SetNICForwarding(tcpip.NICID(%d), %d, %t)", ci.nicid, netProto, enabled))
}
// setMulticastIPForwardingLocked sets the IP multicast forwarding configuration
// for the interface.
//
// The caller must hold the interface's write lock.
func (ci *adminControlImpl) setMulticastIPForwardingLocked(netProto tcpip.NetworkProtocolNumber, enabled bool) bool {
prevEnabled, err := ci.ns.stack.SetNICMulticastForwarding(tcpip.NICID(ci.nicid), netProto, enabled)
return handleIPForwardingConfigurationResult(prevEnabled, err, fmt.Sprintf("ci.ns.stack.SetNICMulticastForwarding(tcpip.NICID(%d), %d, %t)", ci.nicid, netProto, enabled))
}
func (ci *adminControlImpl) getNetworkEndpoint(netProto tcpip.NetworkProtocolNumber) stack.NetworkEndpoint {
ep, err := ci.ns.stack.GetNetworkEndpoint(tcpip.NICID(ci.nicid), netProto)
if err != nil {
panic(fmt.Sprintf("ci.ns.stack.GetNetworkEndpoint(tcpip.NICID(%d), %d): %s", ci.nicid, netProto, err))
}
return ep
}
func toAdminNudConfiguration(stackNud stack.NUDConfigurations) admin.NudConfiguration {
var adminNud admin.NudConfiguration
adminNud.SetMaxMulticastSolicitations(uint16(stackNud.MaxMulticastProbes))
adminNud.SetMaxUnicastSolicitations(uint16(stackNud.MaxUnicastProbes))
adminNud.SetBaseReachableTime(stackNud.BaseReachableTime.Nanoseconds())
return adminNud
}
func (ci *adminControlImpl) getNUDConfig(netProto tcpip.NetworkProtocolNumber) stack.NUDConfigurations {
config, err := ci.ns.stack.NUDConfigurations(tcpip.NICID(ci.nicid), netProto)
if err != nil {
panic(fmt.Sprintf("ci.ns.stack.NUDConfigurations(tcpip.NICID(%d), %d): %s", ci.nicid, netProto, err))
}
return config
}
func (ci *adminControlImpl) applyNUDConfig(netProto tcpip.NetworkProtocolNumber, nudConfig *admin.NudConfiguration) admin.NudConfiguration {
var previousNudConfig admin.NudConfiguration
// NB: We're reading and updating in place here without acquiring
// locks, this is fine because we don't serve
// fuchsia.net.interfaces.admin.Control concurrently.
stackNudConfig := ci.getNUDConfig(netProto)
needsNudUpdate := false
if nudConfig.HasMaxMulticastSolicitations() {
prev := stackNudConfig.MaxMulticastProbes
stackNudConfig.MaxMulticastProbes = uint32(nudConfig.MaxMulticastSolicitations)
previousNudConfig.SetMaxMulticastSolicitations(uint16(prev))
needsNudUpdate = true
}
if nudConfig.HasMaxUnicastSolicitations() {
prev := stackNudConfig.MaxUnicastProbes
stackNudConfig.MaxUnicastProbes = uint32(nudConfig.MaxUnicastSolicitations)
previousNudConfig.SetMaxUnicastSolicitations(uint16(prev))
needsNudUpdate = true
}
if nudConfig.HasBaseReachableTime() {
prev := stackNudConfig.BaseReachableTime
stackNudConfig.BaseReachableTime = time.Duration(nudConfig.BaseReachableTime)
previousNudConfig.SetBaseReachableTime(prev.Nanoseconds())
needsNudUpdate = true
}
if needsNudUpdate {
if err := ci.ns.stack.SetNUDConfigurations(tcpip.NICID(ci.nicid), netProto, stackNudConfig); err != nil {
panic(fmt.Sprintf("ci.ns.stack.SetNUDConfigurations(tcpip.NICID(%d), %d, %v): %s", ci.nicid, netProto, stackNudConfig, err))
}
}
return previousNudConfig
}
func (ci *adminControlImpl) getIGMPEndpoint() ipv4.IGMPEndpoint {
// We want this to panic if EP does not implement ipv4.IGMPEndpoint.
return ci.getNetworkEndpoint(ipv4.ProtocolNumber).(ipv4.IGMPEndpoint)
}
func (ci *adminControlImpl) getMLDEndpoint() ipv6.MLDEndpoint {
// We want this to panic if EP does not implement ipv6.MLDEndpoint.
return ci.getNetworkEndpoint(ipv6.ProtocolNumber).(ipv6.MLDEndpoint)
}
func toAdminIgmpVersion(v ipv4.IGMPVersion) admin.IgmpVersion {
switch v {
case ipv4.IGMPVersion1:
return admin.IgmpVersionV1
case ipv4.IGMPVersion2:
return admin.IgmpVersionV2
case ipv4.IGMPVersion3:
return admin.IgmpVersionV3
default:
panic(fmt.Sprintf("unrecognized version = %d", v))
}
}
func toAdminMldVersion(v ipv6.MLDVersion) admin.MldVersion {
switch v {
case ipv6.MLDVersion1:
return admin.MldVersionV1
case ipv6.MLDVersion2:
return admin.MldVersionV2
default:
panic(fmt.Sprintf("unrecognized version = %d", v))
}
}
func (ci *adminControlImpl) SetConfiguration(_ fidl.Context, config admin.Configuration) (admin.ControlSetConfigurationResult, error) {
if config.HasIpv4() {
// Loopback does not support forwarding.
if ci.isLoopback() {
if config.Ipv4.HasForwarding() && config.Ipv4.Forwarding {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv4ForwardingUnsupported), nil
}
if config.Ipv4.HasMulticastForwarding() && config.Ipv4.MulticastForwarding {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv4MulticastForwardingUnsupported), nil
}
if config.Ipv4.HasArp() {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorArpNotSupported), nil
}
}
// Make sure the IGMP version (if specified) is supported.
if config.Ipv4.HasIgmp() && config.Ipv4.Igmp.HasVersion() {
switch config.Ipv4.Igmp.Version {
case admin.IgmpVersionV1, admin.IgmpVersionV2, admin.IgmpVersionV3:
default:
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv4IgmpVersionUnsupported), nil
}
}
if config.Ipv4.HasArp() {
if config.Ipv4.Arp.HasNud() {
if config.Ipv4.Arp.Nud.HasMaxMulticastSolicitations() && config.Ipv4.Arp.Nud.MaxMulticastSolicitations == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
if config.Ipv4.Arp.Nud.HasMaxUnicastSolicitations() && config.Ipv4.Arp.Nud.MaxUnicastSolicitations == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
if config.Ipv4.Arp.Nud.HasBaseReachableTime() {
if config.Ipv4.Arp.Nud.BaseReachableTime < 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalNegativeValue), nil
} else if config.Ipv4.Arp.Nud.BaseReachableTime == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
}
}
}
}
if config.HasIpv6() {
// Loopback does not support forwarding.
if ci.isLoopback() {
if config.Ipv6.HasForwarding() && config.Ipv6.Forwarding {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv6ForwardingUnsupported), nil
}
if config.Ipv6.HasMulticastForwarding() && config.Ipv6.MulticastForwarding {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv6MulticastForwardingUnsupported), nil
}
if config.Ipv6.HasNdp() {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorNdpNotSupported), nil
}
}
// Make sure the MLD version (if specified) is supported.
if config.Ipv6.HasMld() && config.Ipv6.Mld.HasVersion() {
switch config.Ipv6.Mld.Version {
case admin.MldVersionV1, admin.MldVersionV2:
default:
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIpv6MldVersionUnsupported), nil
}
}
if config.Ipv6.HasNdp() {
if config.Ipv6.Ndp.HasNud() {
if config.Ipv6.Ndp.Nud.HasMaxMulticastSolicitations() && config.Ipv6.Ndp.Nud.MaxMulticastSolicitations == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
if config.Ipv6.Ndp.Nud.HasMaxUnicastSolicitations() && config.Ipv6.Ndp.Nud.MaxUnicastSolicitations == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
if config.Ipv6.Ndp.Nud.HasBaseReachableTime() {
if config.Ipv6.Ndp.Nud.BaseReachableTime < 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalNegativeValue), nil
} else if config.Ipv6.Ndp.Nud.BaseReachableTime == 0 {
return admin.ControlSetConfigurationResultWithErr(admin.ControlSetConfigurationErrorIllegalZeroValue), nil
}
}
}
}
}
ifs := ci.getNICContext()
ifs.mu.Lock()
defer ifs.mu.Unlock()
var previousConfig admin.Configuration
if config.HasIpv4() {
var previousIpv4Config admin.Ipv4Configuration
ipv4Config := config.Ipv4
if ipv4Config.HasForwarding() {
previousIpv4Config.SetForwarding(ci.setIPForwardingLocked(ipv4.ProtocolNumber, ipv4Config.Forwarding))
}
if ipv4Config.HasMulticastForwarding() {
previousIpv4Config.SetMulticastForwarding(ci.setMulticastIPForwardingLocked(ipv4.ProtocolNumber, ipv4Config.MulticastForwarding))
}
if ipv4Config.HasIgmp() {
var previousIgmpConfig admin.IgmpConfiguration
igmpConfig := ipv4Config.Igmp
if igmpConfig.HasVersion() {
var newVersion ipv4.IGMPVersion
switch igmpConfig.Version {
case admin.IgmpVersionV1:
newVersion = ipv4.IGMPVersion1
case admin.IgmpVersionV2:
newVersion = ipv4.IGMPVersion2
case admin.IgmpVersionV3:
newVersion = ipv4.IGMPVersion3
default:
// We validated IGMP version above.
panic(fmt.Sprintf("unexpected IGMP version = %d", igmpConfig.Version))
}
previousIgmpConfig.SetVersion(toAdminIgmpVersion(ci.getIGMPEndpoint().SetIGMPVersion(newVersion)))
}
previousIpv4Config.SetIgmp(previousIgmpConfig)
}
if ipv4Config.HasArp() {
var previousArpConfig admin.ArpConfiguration
if ipv4Config.Arp.HasNud() {
previousArpConfig.SetNud(ci.applyNUDConfig(ipv4.ProtocolNumber, &ipv4Config.Arp.Nud))
}
previousIpv4Config.SetArp(previousArpConfig)
}
previousConfig.SetIpv4(previousIpv4Config)
}
if config.HasIpv6() {
var previousIpv6Config admin.Ipv6Configuration
ipv6Config := config.Ipv6
if ipv6Config.HasForwarding() {
previousIpv6Config.SetForwarding(ci.setIPForwardingLocked(ipv6.ProtocolNumber, ipv6Config.Forwarding))
}
if ipv6Config.HasMulticastForwarding() {
previousIpv6Config.SetMulticastForwarding(ci.setMulticastIPForwardingLocked(ipv6.ProtocolNumber, ipv6Config.MulticastForwarding))
}
if ipv6Config.HasMld() {
var previousMldConfig admin.MldConfiguration
mldConfig := ipv6Config.Mld
if mldConfig.HasVersion() {
var newVersion ipv6.MLDVersion
switch mldConfig.Version {
case admin.MldVersionV1:
newVersion = ipv6.MLDVersion1
case admin.MldVersionV2:
newVersion = ipv6.MLDVersion2
default:
// We validated MLD version above.
panic(fmt.Sprintf("unexpected MLD version = %d", mldConfig.Version))
}
previousMldConfig.SetVersion(toAdminMldVersion(ci.getMLDEndpoint().SetMLDVersion(newVersion)))
}
previousIpv6Config.SetMld(previousMldConfig)
}
if ipv6Config.HasNdp() {
var previousNdpConfig admin.NdpConfiguration
if ipv6Config.Ndp.HasNud() {
previousNdpConfig.SetNud(ci.applyNUDConfig(ipv6.ProtocolNumber, &ipv6Config.Ndp.Nud))
}
if ipv6Config.Ndp.HasDad() {
_ = syslog.WarnTf(controlName, "ignoring unsupported DAD configuration")
}
previousIpv6Config.SetNdp(previousNdpConfig)
}
previousConfig.SetIpv6(previousIpv6Config)
}
// Invalidate all clients' destination caches, as disabling forwarding may
// cause an existing cached route to become invalid.
ifs.ns.resetDestinationCache()
return admin.ControlSetConfigurationResultWithResponse(admin.ControlSetConfigurationResponse{
PreviousConfig: previousConfig,
}), nil
}
// ipForwardingRLocked gets the IP forwarding configuration for the interface.
//
// The caller must hold the interface's read lock.
func (ci adminControlImpl) ipForwardingRLocked(netProto tcpip.NetworkProtocolNumber) bool {
enabled, err := ci.ns.stack.NICForwarding(tcpip.NICID(ci.nicid), netProto)
return handleIPForwardingConfigurationResult(enabled, err, fmt.Sprintf("ci.ns.stack.NICForwarding(tcpip.NICID(%d), %d)", ci.nicid, netProto))
}
// multicastIPForwardingRLocked gets the IP multicast forwarding configuration
// for the interface.
//
// The caller must hold the interface's read lock.
func (ci adminControlImpl) multicastIPForwardingRLocked(netProto tcpip.NetworkProtocolNumber) bool {
enabled, err := ci.ns.stack.NICMulticastForwarding(tcpip.NICID(ci.nicid), netProto)
return handleIPForwardingConfigurationResult(enabled, err, fmt.Sprintf("ci.ns.stack.NICMulticastForwarding(tcpip.NICID(%d), %d)", ci.nicid, netProto))
}
func (ci *adminControlImpl) GetConfiguration(fidl.Context) (admin.ControlGetConfigurationResult, error) {
ifs := ci.getNICContext()
ifs.mu.RLock()
defer ifs.mu.RUnlock()
var config admin.Configuration
{
var ipv4Config admin.Ipv4Configuration
ipv4Config.SetForwarding(ci.ipForwardingRLocked(ipv4.ProtocolNumber))
ipv4Config.SetMulticastForwarding(ci.multicastIPForwardingRLocked(ipv4.ProtocolNumber))
var igmpConfig admin.IgmpConfiguration
igmpConfig.SetVersion(toAdminIgmpVersion(ci.getIGMPEndpoint().GetIGMPVersion()))
ipv4Config.SetIgmp(igmpConfig)
if !ci.isLoopback() {
var arpConfig admin.ArpConfiguration
arpConfig.SetNud(toAdminNudConfiguration(ci.getNUDConfig(ipv4.ProtocolNumber)))
ipv4Config.SetArp(arpConfig)
}
config.SetIpv4(ipv4Config)
}
{
var ipv6Config admin.Ipv6Configuration
ipv6Config.SetForwarding(ci.ipForwardingRLocked(ipv6.ProtocolNumber))
ipv6Config.SetMulticastForwarding(ci.multicastIPForwardingRLocked(ipv6.ProtocolNumber))
var mldConfig admin.MldConfiguration
mldConfig.SetVersion(toAdminMldVersion(ci.getMLDEndpoint().GetMLDVersion()))
ipv6Config.SetMld(mldConfig)
if !ci.isLoopback() {
var ndpConfig admin.NdpConfiguration
ndpConfig.SetNud(toAdminNudConfiguration(ci.getNUDConfig(ipv6.ProtocolNumber)))
ipv6Config.SetNdp(ndpConfig)
}
config.SetIpv6(ipv6Config)
}
return admin.ControlGetConfigurationResultWithResponse(admin.ControlGetConfigurationResponse{
Config: config,
}), nil
}
type adminControlCollection struct {
mu struct {
sync.Mutex
tearingDown bool
controls map[*adminControlImpl]struct{}
strongRefCount uint
}
}
func (c *adminControlCollection) stopServing() []zx.Channel {
c.mu.Lock()
controls := c.mu.controls
c.mu.controls = nil
c.mu.tearingDown = true
c.mu.Unlock()
for control := range controls {
control.cancelServe()
}
var pendingTerminal []zx.Channel
for control := range controls {
pending := <-control.doneChannel
if pending.Handle().IsValid() {
pendingTerminal = append(pendingTerminal, pending)
}
}
return pendingTerminal
}
func sendControlTerminationReason(pending []zx.Channel, reason admin.InterfaceRemovedReason) {
for _, c := range pending {
eventProxy := admin.ControlEventProxy{Channel: c}
if err := eventProxy.OnInterfaceRemoved(reason); err != nil {
_ = syslog.WarnTf(controlName, "failed to send interface close reason %s: %s", reason, err)
}
// Close the channel iff the event proxy hasn't done it already.
if channel := eventProxy.Channel; channel.Handle().IsValid() {
if err := channel.Close(); err != nil {
_ = syslog.ErrorTf(controlName, "channel.Close() = %s", err)
}
}
}
}
func (ifs *ifState) addAdminConnection(request admin.ControlWithCtxInterfaceRequest, strong bool) {
impl, ctx, cancel := func() (*adminControlImpl, context.Context, context.CancelFunc) {
ifs.adminControls.mu.Lock()
defer ifs.adminControls.mu.Unlock()
// Do not add more connections to an interface that is tearing down.
if ifs.adminControls.mu.tearingDown {
if err := request.Channel.Close(); err != nil {
_ = syslog.ErrorTf(controlName, "request.channel.Close() = %s", err)
}
return nil, nil, nil
}
ctx, cancel := context.WithCancel(context.Background())
impl := &adminControlImpl{
ns: ifs.ns,
nicid: ifs.nicid,
cancelServe: cancel,
// We need a buffer in this channel to accommodate for the Remove
// call, which buffers the request channel here before removing the
// interface.
doneChannel: make(chan zx.Channel, 1),
isStrongRef: strong,
}
ifs.adminControls.mu.controls[impl] = struct{}{}
if impl.isStrongRef {
ifs.adminControls.mu.strongRefCount++
}
return impl, ctx, cancel
}()
if impl == nil {
return
}
go func() {
defer cancel()
defer close(impl.doneChannel)
requestChannel := request.Channel
component.Serve(ctx, &admin.ControlWithCtxStub{Impl: impl}, requestChannel, component.ServeOptions{
Concurrent: false,
KeepChannelAlive: true,
OnError: func(err error) {
_ = syslog.WarnTf(controlName, "%s", err)
},
})
// NB: anonymous function is used to restrict section where the lock is
// held.
ifStateToRemove := func() *ifState {
ifs.adminControls.mu.Lock()
defer ifs.adminControls.mu.Unlock()
wasCanceled := errors.Is(ctx.Err(), context.Canceled)
if keepInterface := func() bool {
// Always proceed with removal if this was a synchronous remove
// request.
if impl.syncRemoval {
return false
}
// Don't consider destroying if not a strong ref.
//
// Note that the implementation can change from a strong to a
// weak ref if Detach is called, which is how we allow
// interfaces to leak.
//
// This is also how we prevent destruction from interfaces
// created with the legacy API, since they never have strong
// refs.
if !impl.isStrongRef {
return true
}
ifs.adminControls.mu.strongRefCount--
// If serving was canceled, that means that removal happened due
// to outside cancelation already.
if wasCanceled {
return true
}
// Don't destroy if there are any strong refs left.
if ifs.adminControls.mu.strongRefCount != 0 {
return true
}
return false
}(); keepInterface {
if wasCanceled {
// If we were canceled, pass the request channel along so
// it'll receive the epitaph later.
impl.doneChannel <- requestChannel
} else {
delete(ifs.adminControls.mu.controls, impl)
if err := requestChannel.Close(); err != nil {
_ = syslog.ErrorTf(controlName, "requestChannel.Close() = %s", err)
}
}
return nil
}
// We're good to remove this interface.
// Prevent new connections while we're holding the collection lock,
// avoiding races between here and removing the interface below.
ifs.adminControls.mu.tearingDown = true
// Stash the requestChannel in our finalization channel so the
// terminal event is sent later.
impl.doneChannel <- requestChannel
nicInfo, ok := impl.ns.stack.NICInfo()[impl.nicid]
if !ok {
panic(fmt.Sprintf("failed to find interface %d", impl.nicid))
}
// We can safely remove the interface now because we're certain that
// this control impl is not in the collection anymore, so it can't
// deadlock waiting for control interfaces to finish.
return nicInfo.Context.(*ifState)
}()
if ifStateToRemove != nil {
ifStateToRemove.RemoveByUser()
}
}()
}
var _ admin.InstallerWithCtx = (*interfacesAdminInstallerImpl)(nil)
type interfacesAdminInstallerImpl struct {
ns *Netstack
}
func (i *interfacesAdminInstallerImpl) InstallDevice(_ fidl.Context, device network.DeviceWithCtxInterface, deviceControl admin.DeviceControlWithCtxInterfaceRequest) error {
client, err := netdevice.NewClient(context.Background(), &device, &netdevice.SimpleSessionConfigFactory{})
if err != nil {
_ = syslog.WarnTf(controlName, "InstallDevice: %s", err)
_ = deviceControl.Close()
return nil
}
ctx, cancel := context.WithCancel(context.Background())
impl := &interfacesAdminDeviceControlImpl{
ns: i.ns,
deviceClient: client,
}
// Running the device client and serving the FIDL are tied to the same
// context because their lifecycles are linked.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
impl.deviceClient.Run(ctx)
cancel()
}()
go func() {
component.Serve(ctx, &admin.DeviceControlWithCtxStub{Impl: impl}, deviceControl.Channel, component.ServeOptions{
OnError: func(err error) {
_ = syslog.WarnTf(deviceControlName, "%s", err)
},
})
if !impl.detached {
cancel()
}
// Wait for device goroutine to finish before closing the device.
wg.Wait()
if err := impl.deviceClient.Close(); err != nil {
_ = syslog.ErrorTf(deviceControlName, "deviceClient.Close() = %s", err)
}
}()
return nil
}
var _ admin.DeviceControlWithCtx = (*interfacesAdminDeviceControlImpl)(nil)
type interfacesAdminDeviceControlImpl struct {
ns *Netstack
deviceClient *netdevice.Client
detached bool
}
func (d *interfacesAdminDeviceControlImpl) CreateInterface(_ fidl.Context, portId network.PortId, control admin.ControlWithCtxInterfaceRequest, options admin.Options) error {
ifs, closeReason := func() (*ifState, admin.InterfaceRemovedReason) {
port, err := d.deviceClient.NewPort(context.Background(), portId)
if err != nil {
_ = syslog.WarnTf(deviceControlName, "NewPort(_, %d) failed: %s", portId, err)
{
var unsupported *netdevice.InvalidPortOperatingModeError
if errors.As(err, &unsupported) {
return nil, admin.InterfaceRemovedReasonBadPort
}
}
{
var alreadyBound *netdevice.PortAlreadyBoundError
if errors.As(err, &alreadyBound) {
return nil, admin.InterfaceRemovedReasonPortAlreadyBound
}
}
// Assume all other errors are due to problems communicating with the
// port.
return nil, admin.InterfaceRemovedReasonPortClosed
}
defer func() {
if port != nil {
_ = port.Close()
}
}()
var namePrefix string
var linkEndpoint stack.LinkEndpoint
switch mode := port.Mode(); mode {
case netdevice.PortModeEthernet:
namePrefix = "eth"
linkEndpoint = ethernet.New(port)
case netdevice.PortModeIp:
namePrefix = "ip"
linkEndpoint = port
default:
panic(fmt.Sprintf("unknown port mode %d", mode))
}
metric := defaultInterfaceMetric
if options.HasMetric() {
metric = routetypes.Metric(options.GetMetric())
}
ifs, err := d.ns.addEndpoint(
makeEndpointName(namePrefix, options.GetNameWithDefault("")),
linkEndpoint,
port,
port,
metric,
qdiscConfig{numQueues: numQDiscFIFOQueues, queueLen: int(port.TxDepth()) * qdiscTxDepthMultiplier},
)
if err != nil {
_ = syslog.WarnTf(deviceControlName, "addEndpoint failed: %s", err)
var tcpipError *TcpIpError
if errors.As(err, &tcpipError) {
switch tcpipError.Err.(type) {
case *tcpip.ErrDuplicateNICID:
return nil, admin.InterfaceRemovedReasonDuplicateName
}
}
panic(fmt.Sprintf("unexpected error ns.AddEndpoint(..) = %s", err))
}
// Prevent deferred cleanup from running.
port = nil
return ifs, 0
}()
if closeReason != 0 {
proxy := admin.ControlEventProxy{
Channel: control.Channel,
}
if err := proxy.OnInterfaceRemoved(closeReason); err != nil {
_ = syslog.WarnTf(deviceControlName, "failed to write terminal event %s: %s", closeReason, err)
}
_ = control.Close()
return nil
}
ifs.addAdminConnection(control, true /* strong */)
return nil
}
func (d *interfacesAdminDeviceControlImpl) Detach(fidl.Context) error {
d.detached = true
return nil
}