| // 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 |
| // +build !build_with_native_toolchain |
| |
| package netstack |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "strings" |
| "sync" |
| "syscall/zx" |
| "syscall/zx/fidl" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/netdevice" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routes" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/time" |
| "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/admin" |
| |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "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" |
| ) |
| |
| type addressStateProviderCollection struct { |
| nicid tcpip.NICID |
| mu struct { |
| sync.Mutex |
| providers map[tcpip.Address]*adminAddressStateProviderImpl |
| } |
| } |
| |
| // Called when DAD completes. |
| // |
| // Note that `online` must not change when calling this function (`ifState.mu` |
| // must be held). |
| func (pc *addressStateProviderCollection) onDuplicateAddressDetectionCompleteLocked(nicid tcpip.NICID, addr tcpip.Address, online, success bool) { |
| pi, ok := pc.mu.providers[addr] |
| if !ok { |
| return |
| } |
| pi.mu.Lock() |
| defer pi.mu.Unlock() |
| |
| if success { |
| // If DAD completed successfully but the interface is currently offline, do |
| // not set the assignment state to ASSIGNED. |
| if !online { |
| _ = syslog.Warnf("interface %d is offline when DAD completed for %s", pc.nicid, addr) |
| } else { |
| pi.setStateLocked(admin.AddressAssignmentStateAssigned) |
| } |
| } else { |
| delete(pc.mu.providers, addr) |
| pi.onRemoveLocked(admin.AddressRemovalReasonDadFailed) |
| } |
| } |
| |
| func (pc *addressStateProviderCollection) onAddressRemove(addr tcpip.Address) { |
| pc.mu.Lock() |
| defer pc.mu.Unlock() |
| |
| if pi, ok := pc.mu.providers[addr]; ok { |
| delete(pc.mu.providers, addr) |
| pi.onRemove(admin.AddressRemovalReasonUserRemoved) |
| } |
| } |
| |
| func (pc *addressStateProviderCollection) onInterfaceRemove() { |
| pc.mu.Lock() |
| defer pc.mu.Unlock() |
| |
| for addr, pi := range pc.mu.providers { |
| delete(pc.mu.providers, addr) |
| pi.onRemove(admin.AddressRemovalReasonInterfaceRemoved) |
| } |
| } |
| |
| // TODO(https://fxbug.dev/82045): Avoid racing interface up/down against DAD. |
| // Note that this function should be called while holding a lock which prevents |
| // `online` from mutating. |
| func (pc *addressStateProviderCollection) onInterfaceOnlineChangeLocked(online bool) { |
| for _, pi := range pc.mu.providers { |
| pi.setInitialState(online) |
| } |
| } |
| |
| var _ admin.AddressStateProviderWithCtx = (*adminAddressStateProviderImpl)(nil) |
| |
| type adminAddressStateProviderImpl struct { |
| cancelServe context.CancelFunc |
| ready chan struct{} |
| protocolAddr tcpip.ProtocolAddress |
| mu struct { |
| sync.Mutex |
| eventProxy admin.AddressStateProviderEventProxy |
| isHanging bool |
| // NB: lastObserved is the zero value iff the client has yet to observe the |
| // state for the first time. |
| state, lastObserved admin.AddressAssignmentState |
| detached bool |
| } |
| } |
| |
| func (pi *adminAddressStateProviderImpl) setInitialState(online bool) { |
| pi.mu.Lock() |
| defer pi.mu.Unlock() |
| |
| // If DAD won the race and set the assignment state to ASSIGNED already, |
| // leave the state as ASSIGNED rather than setting it to TENTATIVE. |
| if online && pi.mu.state == admin.AddressAssignmentStateAssigned { |
| _ = syslog.WarnTf(addressStateProviderName, "%s already in ASSIGNED state when interface became online", pi.protocolAddr.AddressWithPrefix.Address) |
| } else { |
| // TODO(https://fxbug.dev/82045): Don't assume that DAD is always enabled. |
| pi.setStateLocked(initialAddressAssignmentState(pi.protocolAddr, online)) |
| } |
| } |
| |
| func (pi *adminAddressStateProviderImpl) setStateLocked(state admin.AddressAssignmentState) { |
| pi.mu.state = state |
| if pi.mu.lastObserved != pi.mu.state { |
| syslog.DebugTf(addressStateProviderName, "address %+v state changed from %s to %s", pi.protocolAddr, pi.mu.lastObserved, pi.mu.state) |
| select { |
| case pi.ready <- struct{}{}: |
| default: |
| } |
| } |
| } |
| |
| func (pi *adminAddressStateProviderImpl) onRemove(reason admin.AddressRemovalReason) { |
| pi.mu.Lock() |
| defer pi.mu.Unlock() |
| |
| pi.onRemoveLocked(reason) |
| } |
| |
| func (pi *adminAddressStateProviderImpl) onRemoveLocked(reason admin.AddressRemovalReason) { |
| if err := pi.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(addressStateProviderName, "failed to send OnAddressRemoved(%s) for %s: %s", reason, pi.protocolAddr.AddressWithPrefix.Address, err) |
| } |
| } |
| pi.cancelServe() |
| } |
| |
| // TODO(https://fxbug.dev/80621): Add support for updating an address's |
| // properties (valid and expected lifetimes). |
| func (pi *adminAddressStateProviderImpl) UpdateAddressProperties(_ fidl.Context, addressProperties admin.AddressProperties) error { |
| _ = syslog.WarnTf(addressStateProviderName, "UpdateAddressProperties for %s: not supported", pi.protocolAddr.AddressWithPrefix.Address) |
| |
| pi.onRemove(admin.AddressRemovalReasonUserRemoved) |
| // Always return an error here so we never issue a response to the request. |
| return fmt.Errorf("UpdateAddressPoprties is for %s: not supported", pi.protocolAddr.AddressWithPrefix.Address) |
| } |
| |
| 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) (admin.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, "address %+v observed state: %s", 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 |
| } |
| } |
| } |
| |
| func interfaceAddressToProtocolAddress(addr net.InterfaceAddress) tcpip.ProtocolAddress { |
| var protocolAddr tcpip.ProtocolAddress |
| switch tag := addr.Which(); tag { |
| case net.InterfaceAddressIpv4: |
| protocolAddr.Protocol = ipv4.ProtocolNumber |
| protocolAddr.AddressWithPrefix.Address = tcpip.Address(addr.Ipv4.Addr.Addr[:]) |
| protocolAddr.AddressWithPrefix.PrefixLen = int(addr.Ipv4.PrefixLen) |
| case net.InterfaceAddressIpv6: |
| protocolAddr.Protocol = ipv6.ProtocolNumber |
| protocolAddr.AddressWithPrefix.Address = tcpip.Address(addr.Ipv6.Addr[:]) |
| // TODO(https://fxbug.dev/81929): Don't lie about the prefix length to gVisor. |
| protocolAddr.AddressWithPrefix.PrefixLen = 8 * header.IPv6AddressSize |
| default: |
| panic(fmt.Sprintf("unknown address: %#v", addr)) |
| } |
| return protocolAddr |
| } |
| |
| func initialAddressAssignmentState(protocolAddr tcpip.ProtocolAddress, online bool) admin.AddressAssignmentState { |
| if !online { |
| return admin.AddressAssignmentStateUnavailable |
| } |
| switch protocolAddr.Protocol { |
| case header.IPv4ProtocolNumber: |
| return admin.AddressAssignmentStateAssigned |
| case header.IPv6ProtocolNumber: |
| return admin.AddressAssignmentStateTentative |
| default: |
| panic(fmt.Sprintf("unknown protocol in address %s: %d", protocolAddr.AddressWithPrefix, protocolAddr.Protocol)) |
| } |
| } |
| |
| var _ admin.ControlWithCtx = (*adminControlImpl)(nil) |
| |
| type adminControlImpl struct { |
| ns *Netstack |
| nicid tcpip.NICID |
| cancelServe context.CancelFunc |
| doneChannel chan struct{} |
| // TODO(https://fxbug.dev/85061): encode owned, strong, and weak refs once |
| // cloning Control is allowed. |
| isStrongRef bool |
| } |
| |
| func (ci *adminControlImpl) Enable(fidl.Context) (admin.ControlEnableResult, error) { |
| 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)) |
| } |
| |
| wasEnabled, err := nicInfo.Context.(*ifState).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) { |
| 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)) |
| } |
| |
| wasEnabled, err := nicInfo.Context.(*ifState).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/87963): Detach should only be allowed on OWNED refs |
| // once we allow cloning Control. |
| ci.isStrongRef = false |
| return nil |
| } |
| |
| func (ci *adminControlImpl) AddAddress(_ fidl.Context, interfaceAddr net.InterfaceAddress, parameters admin.AddressParameters, request admin.AddressStateProviderWithCtxInterfaceRequest) error { |
| protocolAddr := interfaceAddressToProtocolAddress(interfaceAddr) |
| addr := protocolAddr.AddressWithPrefix.Address |
| |
| 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)) |
| } |
| ifs := nicInfo.Context.(*ifState) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| impl := adminAddressStateProviderImpl{ |
| ready: make(chan struct{}, 1), |
| cancelServe: cancel, |
| protocolAddr: protocolAddr, |
| } |
| impl.mu.eventProxy.Channel = request.Channel |
| |
| if reason := func() admin.AddressRemovalReason { |
| // TODO(https://fxbug.dev/80621): Add support for address lifetimes. |
| var b strings.Builder |
| if parameters.HasTemporary() { |
| b.WriteString(fmt.Sprintf(" temporary=%t", parameters.GetTemporary())) |
| } |
| if parameters.HasInitialProperties() { |
| properties := parameters.GetInitialProperties() |
| if properties.HasPreferredLifetimeInfo() { |
| b.WriteString(fmt.Sprintf(" preferredLifetimeInfo=%#v", properties.GetPreferredLifetimeInfo())) |
| } |
| if properties.HasValidLifetimeEnd() { |
| b.WriteString(fmt.Sprintf(" validLifetimeEnd=%s", time.Monotonic(properties.GetValidLifetimeEnd()))) |
| } |
| } |
| if unsupportedProperties := b.String(); unsupportedProperties != "" { |
| _ = syslog.WarnTf(controlName, "AddAddress called with unsupported parameters:%s", unsupportedProperties) |
| return admin.AddressRemovalReasonInvalid |
| } |
| |
| if protocolAddr.AddressWithPrefix.PrefixLen > 8*len(protocolAddr.AddressWithPrefix.Address) { |
| return admin.AddressRemovalReasonInvalid |
| } |
| |
| // NB: Must lock `ifState.mu` and then `addressStateProviderCollection.mu` |
| // to prevent interface online changes (which acquire the same locks in |
| // the same order) from interposing a modification to address assignment |
| // state before the `impl` is inserted into the collection. |
| // |
| // `ifState.mu` is released as soon as possible to avoid deadlock issues. |
| online := func() bool { |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| ifs.addressStateProviders.mu.Lock() |
| return ifs.IsUpLocked() |
| }() |
| defer ifs.addressStateProviders.mu.Unlock() |
| |
| if _, ok := ifs.addressStateProviders.mu.providers[addr]; ok { |
| return admin.AddressRemovalReasonAlreadyAssigned |
| } |
| |
| status := ci.ns.addInterfaceAddress(ci.nicid, protocolAddr, false /* addRoute */) |
| _ = syslog.DebugTf(addressStateProviderName, "addInterfaceAddress(%d, %+v, false) = %s", ci.nicid, protocolAddr, status) |
| switch status { |
| case zx.ErrOk: |
| impl.mu.state = initialAddressAssignmentState(protocolAddr, online) |
| _ = syslog.DebugTf(addressStateProviderName, "initial state for %+v: %s", protocolAddr, impl.mu.state) |
| ifs.addressStateProviders.mu.providers[addr] = &impl |
| return 0 |
| case zx.ErrInvalidArgs: |
| return admin.AddressRemovalReasonInvalid |
| case zx.ErrNotFound: |
| return admin.AddressRemovalReasonInterfaceRemoved |
| case zx.ErrAlreadyExists: |
| return admin.AddressRemovalReasonAlreadyAssigned |
| default: |
| panic(fmt.Errorf("unexpected internal AddAddress error %s", status)) |
| return admin.AddressRemovalReasonUserRemoved |
| } |
| }(); reason != 0 { |
| 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, "failed to send OnAddressRemoved(%s) for %s: %s", reason, protocolAddr.AddressWithPrefix.Address, err) |
| } |
| } |
| if err := impl.mu.eventProxy.Close(); err != nil { |
| _ = syslog.WarnTf(controlName, "failed to close %s channel", addressStateProviderName) |
| } |
| return nil |
| } |
| |
| go func() { |
| component.Serve(ctx, &admin.AddressStateProviderWithCtxStub{Impl: &impl}, request.Channel, component.ServeOptions{ |
| Concurrent: true, |
| OnError: func(err error) { |
| _ = syslog.WarnTf(addressStateProviderName, "address state provider for %s: %s", addr, err) |
| }, |
| }) |
| |
| if pi, ok := func() (*adminAddressStateProviderImpl, bool) { |
| ifs.addressStateProviders.mu.Lock() |
| defer ifs.addressStateProviders.mu.Unlock() |
| |
| // The impl may have already been removed due to address removal. |
| pi, ok := ifs.addressStateProviders.mu.providers[addr] |
| if ok { |
| // Removing the address will also attempt to delete from |
| // the address state providers map, so delete from it and unlock |
| // immediately to avoid lock ordering and deadlock issues. |
| delete(ifs.addressStateProviders.mu.providers, addr) |
| } |
| return pi, ok |
| }(); ok { |
| pi.mu.Lock() |
| remove := !pi.mu.detached |
| pi.mu.Unlock() |
| |
| if remove { |
| // NB: Removing the address will also attempt to access the address state |
| // provider collection and delete the impl out of it if found. The lock |
| // on the collection must not be held at this point to prevent the |
| // deadlock. |
| if status := ci.ns.removeInterfaceAddress(ci.nicid, pi.protocolAddr, false /* removeRoute */); status != zx.ErrOk && status != zx.ErrNotFound { |
| // If address has already been removed, don't consider it an error. |
| _ = syslog.ErrorTf(addressStateProviderName, "failed to remove address %s on channel closure: %s", addr, status) |
| } |
| } |
| } |
| }() |
| return nil |
| } |
| |
| func (ci *adminControlImpl) RemoveAddress(_ fidl.Context, address net.InterfaceAddress) (admin.ControlRemoveAddressResult, error) { |
| protocolAddr := interfaceAddressToProtocolAddress(address) |
| switch zxErr := ci.ns.removeInterfaceAddress(ci.nicid, protocolAddr, false /* removeRoute */); 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 |
| } |
| |
| type adminControlCollection struct { |
| mu struct { |
| sync.Mutex |
| removalReason admin.InterfaceRemovedReason |
| controls map[*adminControlImpl]struct{} |
| strongRefCount uint |
| } |
| } |
| |
| func (c *adminControlCollection) onInterfaceRemove(reason admin.InterfaceRemovedReason) { |
| c.mu.Lock() |
| controls := c.mu.controls |
| c.mu.controls = nil |
| c.mu.removalReason = reason |
| c.mu.Unlock() |
| |
| for control := range controls { |
| control.cancelServe() |
| } |
| for control := range controls { |
| <-control.doneChannel |
| } |
| } |
| |
| func (ifs *ifState) addAdminConnection(request admin.ControlWithCtxInterfaceRequest, strong bool) { |
| |
| impl, ctx := func() (*adminControlImpl, context.Context) { |
| 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.removalReason != 0 { |
| if err := request.Channel.Close(); err != nil { |
| _ = syslog.ErrorTf(controlName, "request.channel.Close() = %s", err) |
| } |
| return nil, nil |
| } |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| impl := &adminControlImpl{ |
| ns: ifs.ns, |
| nicid: ifs.nicid, |
| cancelServe: cancel, |
| doneChannel: make(chan struct{}), |
| isStrongRef: strong, |
| } |
| |
| ifs.adminControls.mu.controls[impl] = struct{}{} |
| if impl.isStrongRef { |
| ifs.adminControls.mu.strongRefCount++ |
| } |
| |
| return impl, ctx |
| }() |
| if impl == nil { |
| return |
| } |
| |
| go func() { |
| defer close(impl.doneChannel) |
| |
| requestChannel := request.Channel |
| defer func() { |
| if !requestChannel.Handle().IsValid() { |
| return |
| } |
| if err := requestChannel.Close(); err != nil { |
| _ = syslog.ErrorTf(controlName, "requestChannel.Close() = %s", err) |
| } |
| }() |
| |
| 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 wasCanceled { |
| reason := ifs.adminControls.mu.removalReason |
| if reason == 0 { |
| panic("serve context canceled without storing a reason") |
| } |
| eventProxy := admin.ControlEventProxy{Channel: requestChannel} |
| if err := eventProxy.OnInterfaceRemoved(reason); err != nil { |
| _ = syslog.WarnTf(controlName, "failed to send interface close reason %s: %s", reason, err) |
| } |
| // Take the channel back from the proxy, since the proxy *may* have closed |
| // the channel, in which case we want to prevent a double close on |
| // the deferred cleanup. |
| requestChannel = eventProxy.Channel |
| } |
| |
| delete(ifs.adminControls.mu.controls, impl) |
| // 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 nil |
| } |
| ifs.adminControls.mu.strongRefCount-- |
| |
| // If serving was canceled, that means that removal happened due to |
| // outside cancelation already. |
| if wasCanceled { |
| return nil |
| } |
| // Don't destroy if there are any strong refs left. |
| if ifs.adminControls.mu.strongRefCount != 0 { |
| 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.removalReason = admin.InterfaceRemovedReasonUser |
| |
| 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, |
| cancelServe: cancel, |
| } |
| |
| // Running the device client and serving the FIDL are tied to the same |
| // context because their lifecycles are linked. |
| go func() { |
| impl.deviceClient.Run(ctx) |
| impl.cancelServe() |
| }() |
| |
| 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 { |
| // Device lifecycle is tied to channel lifetime. |
| 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 |
| cancelServe context.CancelFunc |
| 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)) |
| } |
| |
| ifs, err := d.ns.addEndpoint( |
| makeEndpointName(namePrefix, options.GetNameWithDefault("")), |
| linkEndpoint, |
| port, |
| port, |
| routes.Metric(options.GetMetricWithDefault(0)), |
| ) |
| 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 |
| } |