| // Copyright 2017 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" |
| "math" |
| "net" |
| "reflect" |
| "sort" |
| "syscall/zx" |
| "time" |
| "unsafe" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dhcp" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dns" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/filter" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/bridge" |
| "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" |
| zxtime "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/time" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/tracing/trace" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util" |
| syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go" |
| |
| "fidl/fuchsia/hardware/network" |
| "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/link/loopback" |
| "gvisor.dev/gvisor/pkg/tcpip/link/packetsocket" |
| "gvisor.dev/gvisor/pkg/tcpip/link/qdisc/fifo" |
| "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| const ( |
| defaultInterfaceMetric routetypes.Metric = 100 |
| |
| lowPriorityRoute routetypes.Metric = 99999 |
| |
| dhcpAcquisition = 60 * zxtime.Second |
| dhcpBackoff = 1 * zxtime.Second |
| dhcpRetransmission = 4 * zxtime.Second |
| |
| // Devices do not support multiple queues yet. |
| numQDiscFIFOQueues = 1 |
| // A multiplier to apply to a device's TX Depth to calculate qdisc's queue |
| // length. |
| // |
| // A large enough value was chosen through experimentation to handle sudden |
| // bursts of traffic. |
| qdiscTxDepthMultiplier = 20 |
| ) |
| |
| var ( |
| ipv4Loopback = tcpip.AddrFrom4([4]byte{127, 0, 0, 1}) |
| ipv6Loopback = tcpip.AddrFrom16([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}) |
| ) |
| |
| func ipv6LinkLocalOnLinkRoute(nicID tcpip.NICID) tcpip.Route { |
| return onLinkV6Route(nicID, header.IPv6LinkLocalPrefix.Subnet()) |
| } |
| |
| // The IPv4 Multicast Subnet (224.0.0.0/4), as defined in RFC 1112 section 4. |
| func ipv4MulticastSubnet() tcpip.AddressWithPrefix { |
| return tcpip.AddressWithPrefix{ |
| Address: util.Parse("224.0.0.0"), |
| PrefixLen: 4, |
| } |
| } |
| |
| // The IPv6 Multicast Subnet (ff00::/8), as defined in RFC 4291 section 2.7. |
| func ipv6MulticastSubnet() tcpip.AddressWithPrefix { |
| return tcpip.AddressWithPrefix{ |
| Address: util.Parse("ff00::"), |
| PrefixLen: 8, |
| } |
| } |
| |
| type MaxSocketOptionStats struct { |
| NetworkSocketOptionStats |
| StreamSocketSpecificOptionStats |
| RawSocketSpecificOptionStats |
| } |
| |
| func (s *MaxSocketOptionStats) updateMax(sockOptStats socketOptionStats) { |
| updateMax := func(max, candidate any) { |
| maxValue := reflect.ValueOf(max).Elem() |
| candidateValue := reflect.ValueOf(candidate).Elem() |
| for i := 0; i < maxValue.NumField(); i++ { |
| candidateValue := candidateValue.Field(i).Addr().Interface().(*atomicUint32Stat).Load() |
| max := maxValue.Field(i).Addr().Interface().(*atomicUint32Stat) |
| for { |
| if maxOld := max.Load(); candidateValue > maxOld { |
| if max.CompareAndSwap(maxOld, candidateValue) { |
| break |
| } |
| } else { |
| break |
| } |
| } |
| } |
| } |
| switch sockOptStats := sockOptStats.(type) { |
| case *NetworkSocketOptionStats: |
| updateMax(&s.NetworkSocketOptionStats, sockOptStats) |
| case *streamSocketOptionStats: |
| updateMax(&s.NetworkSocketOptionStats, sockOptStats.NetworkSocketOptionStats) |
| updateMax(&s.StreamSocketSpecificOptionStats, &sockOptStats.StreamSocketSpecificOptionStats) |
| case *rawSocketOptionStats: |
| updateMax(&s.NetworkSocketOptionStats, sockOptStats.NetworkSocketOptionStats) |
| updateMax(&s.RawSocketSpecificOptionStats, &sockOptStats.RawSocketSpecificOptionStats) |
| default: |
| panic(fmt.Sprintf("unknown socket option stat type: %T", sockOptStats)) |
| } |
| } |
| |
| type stats struct { |
| tcpip.Stats |
| SocketCount tcpip.StatCounter |
| SocketsCreated tcpip.StatCounter |
| SocketsDestroyed tcpip.StatCounter |
| DHCPv6 struct { |
| NoConfiguration tcpip.StatCounter |
| ManagedAddress tcpip.StatCounter |
| OtherConfiguration tcpip.StatCounter |
| } |
| IPv6AddressConfig struct { |
| NoGlobalSLAACOrDHCPv6ManagedAddress tcpip.StatCounter |
| GlobalSLAACOnly tcpip.StatCounter |
| DHCPv6ManagedAddressOnly tcpip.StatCounter |
| GlobalSLAACAndDHCPv6ManagedAddress tcpip.StatCounter |
| } |
| // MaxSocketOptionStats holds the stack-wide maximum of each socket option |
| // stat across the lifetime of the stack. |
| // |
| // Note that because this is updated whenever a socket is closed, and on every |
| // inspect fetch, the values returned via inspect are always accurate, but the |
| // values contained within may not be the stack-wide max at any moment in time. |
| MaxSocketOptionStats MaxSocketOptionStats |
| } |
| |
| type endpointAndStats struct { |
| ep tcpip.Endpoint |
| sockOptStats socketOptionStats |
| } |
| |
| // endpointsMap is a map from a monotonically increasing uint64 value to |
| // endpointAndStats. |
| // |
| // It is a typesafe wrapper around sync.Map. |
| type endpointsMap struct { |
| nextKey uint64 |
| inner sync.Map |
| } |
| |
| func (m *endpointsMap) CompareAndSwap(key uint64, old, new endpointAndStats) bool { |
| return m.inner.CompareAndSwap(key, old, new) |
| } |
| |
| func (m *endpointsMap) Load(key uint64) (endpointAndStats, bool) { |
| if value, ok := m.inner.Load(key); ok { |
| return value.(endpointAndStats), true |
| } |
| return endpointAndStats{}, false |
| } |
| |
| func (m *endpointsMap) Store(key uint64, value endpointAndStats) { |
| m.inner.Store(key, value) |
| } |
| |
| func (m *endpointsMap) LoadOrStore(key uint64, value endpointAndStats) (endpointAndStats, bool) { |
| // Create a scope to allow `value` to be shadowed below. |
| { |
| value, ok := m.inner.LoadOrStore(key, value) |
| return value.(endpointAndStats), ok |
| } |
| } |
| |
| func (m *endpointsMap) LoadAndDelete(key uint64) (endpointAndStats, bool) { |
| if value, ok := m.inner.LoadAndDelete(key); ok { |
| return value.(endpointAndStats), ok |
| } |
| return endpointAndStats{}, false |
| } |
| |
| func (m *endpointsMap) Delete(key uint64) { |
| m.inner.Delete(key) |
| } |
| |
| func (m *endpointsMap) Range(f func(key uint64, value endpointAndStats) bool) { |
| m.inner.Range(func(key, value interface{}) bool { |
| return f(key.(uint64), value.(endpointAndStats)) |
| }) |
| } |
| |
| // NICRemovedHandler is an interface implemented by types that are interested |
| // in NICs that have been removed. |
| type NICRemovedHandler interface { |
| // RemovedNIC informs the receiver that the specified NIC has been removed. |
| RemovedNIC(tcpip.NICID) |
| } |
| |
| // A Netstack tracks all of the running state of the network stack. |
| type Netstack struct { |
| dnsConfig dns.ServersConfig |
| |
| interfaceEventChan chan<- interfaceEvent |
| |
| stack *stack.Stack |
| routeTable routes.RouteTable |
| |
| mu struct { |
| sync.Mutex |
| countNIC tcpip.NICID |
| } |
| |
| destinationCacheMu struct { |
| sync.RWMutex |
| destinationCache destinationCache |
| } |
| |
| stats stats |
| |
| endpoints endpointsMap |
| |
| nicRemovedHandlers []NICRemovedHandler |
| |
| featureFlags featureFlags |
| } |
| |
| // Flags for turning on functionality in select environments. |
| type featureFlags struct { |
| enableFastUDP bool |
| } |
| |
| // Each ifState tracks the state of a network interface. |
| type ifState struct { |
| ns *Netstack |
| // Implements administrative control of link status. |
| // |
| // Non-nil iff the underlying link status can be toggled. |
| controller link.Controller |
| // Implements observation of link status. |
| // |
| // Non-nil iff the underlying link status can be observed. |
| observer link.Observer |
| nicid tcpip.NICID |
| // TODO(https://fxbug.dev/42178599): This lock is unnecessary in that we would |
| // rather reuse `ifState.mu` if not for the fact that mutexes cannot be used |
| // with select. Consolidate and remove this lock. |
| // Lock for DHCP client's access to ifState. |
| // |
| // Must write to this channel before acquiring ifState.mu on all paths that |
| // may cancel the DHCP client. Before the DHCP client needs to hold |
| // ifState.mu, it will select on this channel and its context done |
| // channel, and only proceed if it is able to write to the channel. |
| dhcpLock chan struct{} |
| mu struct { |
| sync.RWMutex |
| adminUp, linkOnline, removed bool |
| dhcp struct { |
| *dhcp.Client |
| // running must not be nil. |
| running func() bool |
| // cancelLocked must not be nil. |
| // |
| // ifState.mu must be locked before calling this function. |
| cancelLocked context.CancelFunc |
| // Used to restart the DHCP client when we go from down to up. |
| enabled bool |
| } |
| } |
| |
| // metric is used by default for routes that originate from this NIC. |
| metric routetypes.Metric |
| |
| adminControls adminControlCollection |
| |
| // authorizationToken is a Zircon object used to prove client ownership of |
| // this interface. |
| authorizationToken zx.Event |
| |
| dns struct { |
| mu struct { |
| sync.Mutex |
| servers []tcpip.Address |
| } |
| } |
| |
| // The "outermost" LinkEndpoint implementation (the composition of link |
| // endpoint functionality happens by wrapping other link endpoints). |
| endpoint stack.LinkEndpoint |
| |
| bridgeable *bridge.BridgeableEndpoint |
| |
| // TODO(https://fxbug.dev/42167700): Bridged interfaces are disabled within |
| // gVisor upon creation and thus the bridge must keep track of them |
| // in order to re-enable them when the bridge is removed. This is a |
| // hack, and should be replaced with a proper bridging implementation. |
| bridgedInterfaces []tcpip.NICID |
| } |
| |
| func (ifs *ifState) LinkOnlineLocked() bool { |
| return ifs.observer == nil || ifs.mu.linkOnline |
| } |
| |
| func (ifs *ifState) IsUpLocked() bool { |
| return ifs.mu.adminUp && ifs.LinkOnlineLocked() |
| } |
| |
| // defaultV4Route returns a default IPv4 route through gateway on the specified |
| // NIC. |
| func defaultV4Route(nicid tcpip.NICID, gateway tcpip.Address) tcpip.Route { |
| return tcpip.Route{ |
| Destination: header.IPv4EmptySubnet, |
| Gateway: gateway, |
| NIC: nicid, |
| } |
| } |
| |
| // onLinkV6Route returns an on-link route to dest through the specified NIC. |
| // |
| // dest must be a subnet that is directly reachable by the specified NIC as |
| // an on-link route is a route to a subnet that a NIC is directly connected to. |
| func onLinkV6Route(nicID tcpip.NICID, dest tcpip.Subnet) tcpip.Route { |
| return tcpip.Route{ |
| Destination: dest, |
| NIC: nicID, |
| } |
| } |
| |
| func addressWithPrefixRoute(nicid tcpip.NICID, addr tcpip.AddressWithPrefix) tcpip.Route { |
| mask := net.CIDRMask(addr.PrefixLen, addr.Address.BitLen()) |
| destination, err := tcpip.NewSubnet(tcpip.AddrFromSlice(net.IP(addr.Address.AsSlice()).Mask(mask)), tcpip.MaskFromBytes(mask)) |
| if err != nil { |
| panic(err) |
| } |
| |
| return tcpip.Route{ |
| Destination: destination, |
| NIC: nicid, |
| } |
| } |
| |
| func (ns *Netstack) resetDestinationCache() { |
| ns.destinationCacheMu.Lock() |
| trace.AsyncBegin("net", "netstack.resetDestinationCache", trace.AsyncID(uintptr(unsafe.Pointer(ns)))) |
| ns.destinationCacheMu.destinationCache.reset() |
| trace.AsyncEnd("net", "netstack.resetDestinationCache", trace.AsyncID(uintptr(unsafe.Pointer(ns)))) |
| ns.destinationCacheMu.Unlock() |
| } |
| |
| func (ns *Netstack) name(nicid tcpip.NICID) string { |
| name := ns.stack.FindNICNameFromID(nicid) |
| if len(name) == 0 { |
| name = fmt.Sprintf("unknown(NICID=%d)", nicid) |
| } |
| return name |
| } |
| |
| func (ns *Netstack) fillRouteNIC(r tcpip.Route) (tcpip.Route, error) { |
| // If we don't have an interface set, find it using the gateway address. |
| if r.NIC == 0 { |
| nic, err := ns.routeTable.FindNIC(r.Gateway) |
| if err != nil { |
| return tcpip.Route{}, fmt.Errorf("error finding NIC for gateway %s: %w", r.Gateway, err) |
| } |
| r.NIC = nic |
| } |
| return r, nil |
| } |
| |
| // AddRoute adds a single route to the route table in a sorted fashion. |
| func (ns *Netstack) AddRoute(r tcpip.Route, metric *routetypes.Metric, dynamic, replaceMatchingGvisorRoutes bool, addingSet *routetypes.RouteSetId) (routes.AddResult, error) { |
| return ns.addRouteWithPreference(r, routetypes.MediumPreference, metric, dynamic, replaceMatchingGvisorRoutes, addingSet) |
| } |
| |
| func (ns *Netstack) addRouteWithPreference(r tcpip.Route, prf routetypes.Preference, metric *routetypes.Metric, dynamic, replaceMatchingGvisorRoutes bool, addingSet *routetypes.RouteSetId) (routes.AddResult, error) { |
| r, err := ns.fillRouteNIC(r) |
| if err != nil { |
| return routes.AddResult{}, err |
| } |
| return ns.addRoutesWithPreference(r.NIC, []tcpip.Route{r}, prf, metric, dynamic, replaceMatchingGvisorRoutes, addingSet) |
| } |
| |
| // addRoutesWithPreference adds routes for the same interface to the route table |
| // with a configurable preference value. |
| // |
| // All routes in `rs` will have their NICID rewritten to `nicid`. |
| func (ns *Netstack) addRoutesWithPreference(nicid tcpip.NICID, rs []tcpip.Route, prf routetypes.Preference, metric *routetypes.Metric, dynamic, replaceMatchingGvisorRoutes bool, addingSet *routetypes.RouteSetId) (routes.AddResult, error) { |
| nicInfo, ok := ns.stack.NICInfo()[nicid] |
| if !ok { |
| return routes.AddResult{}, fmt.Errorf("error getting nicInfo for NIC %d, not in map: %w", nicid, routes.ErrNoSuchNIC) |
| } |
| ifs := nicInfo.Context.(*ifState) |
| |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| return ifs.addRoutesWithPreferenceLocked(rs, prf, metric, dynamic, replaceMatchingGvisorRoutes, addingSet), nil |
| } |
| |
| // addRoutesWithPreferenceLocked returns an AddResult OR-ed across the AddResults for each individual route added. |
| // If metric is nil, the route's metric tracks its interface's metric. |
| func (ifs *ifState) addRoutesWithPreferenceLocked(rs []tcpip.Route, prf routetypes.Preference, metric *routetypes.Metric, dynamic, overwriteIfAlreadyExist bool, addingSet *routetypes.RouteSetId) routes.AddResult { |
| metricTracksInterface := false |
| if metric == nil { |
| metricTracksInterface = true |
| } |
| |
| enabled := ifs.IsUpLocked() |
| if metricTracksInterface { |
| metric = &ifs.metric |
| } |
| |
| ifs.ns.routeTable.Lock() |
| defer ifs.ns.routeTable.Unlock() |
| |
| var v4DefaultRouteAdded, v6DefaultRouteAdded bool |
| var aggregateAddResult routes.AddResult |
| |
| // We avoid computing the set of NICs with default routes unless necessary, |
| // and then cache the result to avoid computing it more than once. |
| var nicsWithDefaultV4Route map[tcpip.NICID]struct{} |
| var nicsWithDefaultV6Route map[tcpip.NICID]struct{} |
| |
| hasDefaultRouteV4 := func(nic tcpip.NICID) bool { |
| if nicsWithDefaultV4Route == nil { |
| nicsWithDefaultV4Route = ifs.ns.routeTable.GetNICsWithDefaultV4RoutesLocked() |
| } |
| _, ok := nicsWithDefaultV4Route[nic] |
| return ok |
| } |
| |
| hasDefaultRouteV6 := func(nic tcpip.NICID) bool { |
| if nicsWithDefaultV6Route == nil { |
| nicsWithDefaultV6Route = ifs.ns.routeTable.GetNICsWithDefaultV6RoutesLocked() |
| } |
| _, ok := nicsWithDefaultV6Route[nic] |
| return ok |
| } |
| |
| for _, r := range rs { |
| r.NIC = ifs.nicid |
| |
| if enabled { |
| // It's possible that a default route with a different metric has |
| // previously been added, so we should check that there wasn't already |
| // a default route before deciding to send an event indicating that we |
| // have newly acquired a default route. |
| if r.Destination.Equal(header.IPv4EmptySubnet) && !hasDefaultRouteV4(r.NIC) { |
| v4DefaultRouteAdded = true |
| } else if r.Destination.Equal(header.IPv6EmptySubnet) && !hasDefaultRouteV6(r.NIC) { |
| v6DefaultRouteAdded = true |
| } |
| } |
| |
| addResult := ifs.ns.routeTable.AddRouteLocked(r, prf, *metric, metricTracksInterface, dynamic, enabled, overwriteIfAlreadyExist, addingSet) |
| aggregateAddResult.NewlyAddedToSet = aggregateAddResult.NewlyAddedToSet || addResult.NewlyAddedToSet |
| aggregateAddResult.NewlyAddedToTable = aggregateAddResult.NewlyAddedToTable || addResult.NewlyAddedToTable |
| _ = syslog.Infof("adding route [%s] prf=%d metric=%d dynamic=%t", r, prf, metric, dynamic) |
| |
| } |
| |
| ifs.ns.routeTable.UpdateStackLocked(ifs.ns.stack, ifs.ns.resetDestinationCache) |
| |
| if v4DefaultRouteAdded { |
| ifs.ns.onDefaultIPv4RouteChangeLocked(ifs.nicid, true /* hasDefaultRoute */) |
| } |
| if v6DefaultRouteAdded { |
| ifs.ns.onDefaultIPv6RouteChangeLocked(ifs.nicid, true /* hasDefaultRoute */) |
| } |
| |
| return aggregateAddResult |
| } |
| |
| // DelRouteExactMatch deletes a route from the routing table if there is an |
| // extended route with the same tcpip.Route and metric, and default preference. |
| // If metric is nil, the route's metric tracks its interface's metric. |
| func (ns *Netstack) DelRouteExactMatch(r tcpip.Route, metric *routetypes.Metric, set *routetypes.RouteSetId) (routes.DelResult, error) { |
| if r.NIC == 0 { |
| return routes.DelResult{}, fmt.Errorf("unspecified NIC in route %#v", r) |
| } |
| |
| nicInfo, ok := ns.stack.NICInfo()[r.NIC] |
| if !ok { |
| return routes.DelResult{}, fmt.Errorf("nonexistent NIC in route %#v", r) |
| } |
| |
| ifs := nicInfo.Context.(*ifState) |
| |
| metricTracksInterface := false |
| if metric == nil { |
| metricTracksInterface = true |
| } |
| if metricTracksInterface { |
| metric = &ifs.metric |
| } |
| |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| ns.routeTable.Lock() |
| defer ns.routeTable.Unlock() |
| |
| delResult := ns.routeTable.DelRouteExactMatchLocked(r, routetypes.MediumPreference, *metric, metricTracksInterface, set) |
| if delResult.NewlyRemovedFromTable { |
| ns.routeTable.UpdateStackLocked(ns.stack, ns.resetDestinationCache) |
| |
| if r.Destination.Equal(header.IPv4EmptySubnet) { |
| if _, ok := ns.routeTable.GetNICsWithDefaultV4RoutesLocked()[r.NIC]; !ok { |
| ns.onDefaultIPv4RouteChangeLocked(r.NIC, false /* hasDefaultRoute */) |
| } |
| } else if r.Destination.Equal(header.IPv6EmptySubnet) { |
| if _, ok := ns.routeTable.GetNICsWithDefaultV6RoutesLocked()[r.NIC]; !ok { |
| ns.onDefaultIPv6RouteChangeLocked(r.NIC, false /* hasDefaultRoute */) |
| } |
| } |
| } |
| return delResult, nil |
| } |
| |
| // DelRouteSet deletes a route set, decrementing the reference count for every |
| // route in the set. If the route set is global, then no changes are made. |
| func (ns *Netstack) DelRouteSet(set *routetypes.RouteSetId) { |
| // There is a check for IsGlobal in (*RouteTable.DelRouteSetLocked) to |
| // prevent accidentally deleting all routes. This is just to prevent |
| // useless work from being done. |
| if set.IsGlobal() { |
| return |
| } |
| |
| ns.mu.Lock() |
| defer ns.mu.Unlock() |
| |
| // Needed to get consistent iteration order. |
| var ifStates []*ifState |
| |
| for _, nicInfo := range ns.stack.NICInfo() { |
| ifs := nicInfo.Context.(*ifState) |
| ifStates = append(ifStates, ifs) |
| } |
| |
| sort.Slice(ifStates, func(i, j int) bool { |
| return ifStates[i].nicid < ifStates[j].nicid |
| }) |
| |
| for _, ifs := range ifStates { |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| } |
| |
| ns.routeTable.Lock() |
| defer ns.routeTable.Unlock() |
| |
| routesDeleted := ns.routeTable.DelRouteSetLocked(set) |
| |
| if len(routesDeleted) > 0 { |
| ns.routeTable.UpdateStackLocked(ns.stack, ns.resetDestinationCache) |
| } |
| |
| nicsWithDeletedV4DefaultRoutes := make(map[tcpip.NICID]struct{}) |
| nicsWithDeletedV6DefaultRoutes := make(map[tcpip.NICID]struct{}) |
| |
| for _, route := range routesDeleted { |
| if route.Route.Destination.Equal(header.IPv4EmptySubnet) { |
| nicsWithDeletedV4DefaultRoutes[route.Route.NIC] = struct{}{} |
| } |
| if route.Route.Destination.Equal(header.IPv6EmptySubnet) { |
| nicsWithDeletedV6DefaultRoutes[route.Route.NIC] = struct{}{} |
| } |
| } |
| |
| // It's possible that there were multiple copies of the default route with different |
| // metrics, so we need to check whether there are any default routes still present |
| // after these routes were removed. |
| nicsWithV4DefaultRoutes := ns.routeTable.GetNICsWithDefaultV4RoutesLocked() |
| nicsWithV6DefaultRoutes := ns.routeTable.GetNICsWithDefaultV6RoutesLocked() |
| |
| for nic := range nicsWithDeletedV4DefaultRoutes { |
| if _, ok := nicsWithV4DefaultRoutes[nic]; !ok { |
| ns.onDefaultIPv4RouteChangeLocked(nic, false /* hasDefaultRoute */) |
| } |
| } |
| for nic := range nicsWithDeletedV6DefaultRoutes { |
| if _, ok := nicsWithV6DefaultRoutes[nic]; !ok { |
| ns.onDefaultIPv6RouteChangeLocked(nic, false /* hasDefaultRoute */) |
| } |
| } |
| } |
| |
| // delRouteLocked deletes routes from a single interface identified by `r.NIC`. |
| // |
| // The `ifState` of the interface identified by `r.NIC` must be locked for the |
| // duration of this function. |
| func (ns *Netstack) delRouteLocked(r tcpip.Route, deletingSet *routetypes.RouteSetId) []routetypes.ExtendedRoute { |
| ns.routeTable.Lock() |
| defer ns.routeTable.Unlock() |
| |
| routesDeleted := ns.routeTable.DelRouteLocked(r, deletingSet) |
| if len(routesDeleted) == 0 { |
| return nil |
| } |
| |
| ns.routeTable.UpdateStackLocked(ns.stack, ns.resetDestinationCache) |
| |
| for _, er := range routesDeleted { |
| if er.Enabled { |
| if er.Route.Destination.Equal(header.IPv4EmptySubnet) { |
| ns.onDefaultIPv4RouteChangeLocked(er.Route.NIC, false /* hasDefaultRoute */) |
| } else if er.Route.Destination.Equal(header.IPv6EmptySubnet) { |
| ns.onDefaultIPv6RouteChangeLocked(er.Route.NIC, false /* hasDefaultRoute */) |
| } |
| } |
| } |
| |
| return routesDeleted |
| } |
| |
| // DelRoute deletes all routes matching r from the route table. |
| func (ns *Netstack) DelRoute(r tcpip.Route, deletingSet *routetypes.RouteSetId) []routetypes.ExtendedRoute { |
| _ = syslog.Infof("deleting route %s", r) |
| |
| nicInfoMap := ns.stack.NICInfo() |
| |
| delRoute := func(nicInfo stack.NICInfo, r tcpip.Route) []routetypes.ExtendedRoute { |
| ifs := nicInfo.Context.(*ifState) |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| return ns.delRouteLocked(r, deletingSet) |
| } |
| |
| if r.NIC == 0 { |
| var routesDeleted []routetypes.ExtendedRoute |
| for nicid, nicInfo := range nicInfoMap { |
| r.NIC = nicid |
| routesDeleted = append(routesDeleted, delRoute(nicInfo, r)...) |
| } |
| return routesDeleted |
| } else { |
| nicInfo, ok := nicInfoMap[r.NIC] |
| if !ok { |
| return nil |
| } |
| return delRoute(nicInfo, r) |
| } |
| } |
| |
| // GetExtendedRouteTable returns a copy of the current extended route table. |
| func (ns *Netstack) GetExtendedRouteTable() []routetypes.ExtendedRoute { |
| return ns.routeTable.GetExtendedRouteTable() |
| } |
| |
| // UpdateRoutesByInterface applies update actions to the routes for a |
| // given interface. |
| func (ns *Netstack) UpdateRoutesByInterfaceLocked(nicid tcpip.NICID, action routetypes.Action) { |
| ns.routeTable.Lock() |
| defer ns.routeTable.Unlock() |
| |
| ns.routeTable.UpdateRoutesByInterfaceLocked(nicid, action) |
| |
| hasDefaultIPv4Route, hasDefaultIPv6Route := ns.routeTable.HasDefaultRouteLocked(nicid) |
| ns.onDefaultRouteChangeLocked(nicid, hasDefaultIPv4Route, hasDefaultIPv6Route) |
| |
| ns.routeTable.UpdateStackLocked(ns.stack, ns.resetDestinationCache) |
| } |
| |
| func (ifs *ifState) removeAddress(protocolAddr tcpip.ProtocolAddress) zx.Status { |
| _ = syslog.Infof("NIC %d: removing IP %s", ifs.nicid, protocolAddr.AddressWithPrefix) |
| |
| switch err := ifs.ns.stack.RemoveAddress(ifs.nicid, protocolAddr.AddressWithPrefix.Address); err.(type) { |
| case nil: |
| case *tcpip.ErrUnknownNICID: |
| _ = syslog.Warnf("stack.RemoveAddress(%d, %s): NIC not found", ifs.nicid, protocolAddr.AddressWithPrefix) |
| return zx.ErrBadState |
| case *tcpip.ErrBadLocalAddress: |
| return zx.ErrNotFound |
| default: |
| panic(fmt.Sprintf("stack.RemoveAddress(%d, %s) = %s", ifs.nicid, protocolAddr.AddressWithPrefix, err)) |
| } |
| |
| ifs.ns.resetDestinationCache() |
| return zx.ErrOk |
| } |
| |
| type addressChanged struct { |
| nicid tcpip.NICID |
| protocolAddr tcpip.ProtocolAddress |
| lifetimes stack.AddressLifetimes |
| state stack.AddressAssignmentState |
| } |
| |
| var _ interfaceEvent = (*addressChanged)(nil) |
| |
| func (*addressChanged) isInterfaceEvent() {} |
| |
| func (a addressChanged) String() string { |
| return fmt.Sprintf( |
| "{nicid:%d addr:%s lifetimes:{Deprecated:%t PreferredUntil:boot+%s ValidUntil:boot+%s} state:%s}", |
| a.nicid, |
| a.protocolAddr.AddressWithPrefix, |
| a.lifetimes.Deprecated, |
| a.lifetimes.PreferredUntil.Sub(tcpip.MonotonicTime{}), |
| a.lifetimes.ValidUntil.Sub(tcpip.MonotonicTime{}), |
| a.state, |
| ) |
| } |
| |
| type addressRemoved struct { |
| nicid tcpip.NICID |
| protocolAddr tcpip.ProtocolAddress |
| reason stack.AddressRemovalReason |
| } |
| |
| var _ interfaceEvent = (*addressRemoved)(nil) |
| |
| func (*addressRemoved) isInterfaceEvent() {} |
| |
| func (a addressRemoved) String() string { |
| return fmt.Sprintf("{nicid:%d addr:%s reason:%s}", a.nicid, a.protocolAddr.AddressWithPrefix, a.reason) |
| } |
| |
| var _ stack.AddressDispatcher = (*watcherAddressDispatcher)(nil) |
| |
| type watcherAddressDispatcher struct { |
| nicid tcpip.NICID |
| protocolAddr tcpip.ProtocolAddress |
| ch chan<- interfaceEvent |
| } |
| |
| // OnChanged is called when the address this AddressDispatcher is registered |
| // on is assigned or changed. |
| // |
| // Note that this function is called while locked inside gVisor, so care should |
| // be taken to avoid deadlock. |
| func (ad *watcherAddressDispatcher) OnChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) { |
| _ = syslog.Debugf("NIC=%d addr=%s changed lifetimes=%#v state=%s", |
| ad.nicid, ad.protocolAddr.AddressWithPrefix, lifetimes, state) |
| if ad.ch != nil { |
| ad.ch <- &addressChanged{ |
| nicid: ad.nicid, |
| protocolAddr: ad.protocolAddr, |
| lifetimes: lifetimes, |
| state: state, |
| } |
| } |
| } |
| |
| // OnRemoved is called when the address this AddressDispatcher is registered |
| // on is removed. |
| // |
| // Note that this function is called while locked inside gVisor, so care should |
| // be taken to avoid deadlock. |
| func (ad *watcherAddressDispatcher) OnRemoved(reason stack.AddressRemovalReason) { |
| _ = syslog.Debugf("NIC=%d addr=%s removed reason=%s", ad.nicid, ad.protocolAddr.AddressWithPrefix, reason) |
| if ad.ch != nil { |
| ad.ch <- &addressRemoved{ |
| nicid: ad.nicid, |
| protocolAddr: ad.protocolAddr, |
| reason: reason, |
| } |
| } |
| } |
| |
| func (ifs *ifState) addAddress(protocolAddr tcpip.ProtocolAddress, properties stack.AddressProperties) (bool, admin.AddressRemovalReason) { |
| _ = syslog.Infof("NIC %d: adding address %s", ifs.nicid, protocolAddr.AddressWithPrefix) |
| |
| // properties.Disp is non-nil iff the caller is serving |
| // fuchsia.net.interfaces.admin/Control.AddAddress. The |
| // AddressDispatcher implementation used serves both |
| // fuchsia.net.interfaces.admin/AddressStateProvider and |
| // fuchsia.net.interfaces/Watcher. |
| // |
| // If no dispatcher is passed, register one for serving |
| // fuchsia.net.interfaces/Watcher. |
| if properties.Disp == nil && ifs.ns.interfaceEventChan != nil { |
| properties.Disp = &watcherAddressDispatcher{ |
| nicid: ifs.nicid, |
| protocolAddr: protocolAddr, |
| ch: ifs.ns.interfaceEventChan, |
| } |
| } |
| switch err := ifs.ns.stack.AddProtocolAddress(ifs.nicid, protocolAddr, properties); err.(type) { |
| case nil: |
| return true, 0 |
| case *tcpip.ErrUnknownNICID: |
| return false, admin.AddressRemovalReasonInterfaceRemoved |
| case *tcpip.ErrDuplicateAddress: |
| return false, admin.AddressRemovalReasonAlreadyAssigned |
| default: |
| panic(fmt.Sprintf("stack.AddProtocolAddress(%d, %s, %#v) unexpected error: %s", ifs.nicid, protocolAddr.AddressWithPrefix, properties, err)) |
| } |
| } |
| |
| // onInterfaceAddLocked must be called with `ifs.mu` locked. |
| func (ns *Netstack) onInterfaceAddLocked(ifs *ifState, name string) { |
| e := interfaceAdded(initialProperties(ifs, name)) |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &e |
| } |
| } |
| |
| // onInterfaceRemoveLocked must be called with `ifState.mu` of the interface |
| // identified by `nicid` locked. |
| func (ns *Netstack) onInterfaceRemoveLocked(nicid tcpip.NICID) { |
| e := interfaceRemoved(nicid) |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &e |
| } |
| } |
| |
| // onOnlineChangeLocked must be called with `ifState.mu` of the interface |
| // identified by `nicid` locked. |
| func (ns *Netstack) onOnlineChangeLocked(nicid tcpip.NICID, online bool) { |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &onlineChanged{ |
| nicid: nicid, |
| online: online, |
| } |
| } |
| } |
| |
| // onDefaultIPv4RouteChangeLocked must be called with the `ifState.mu` of the |
| // interface identified by `nicid` and the route table locked (in that order) |
| // to avoid races against other route changes. |
| func (ns *Netstack) onDefaultIPv4RouteChangeLocked(nicid tcpip.NICID, hasDefaultRoute bool) { |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &defaultRouteChanged{ |
| nicid: nicid, |
| hasDefaultIPv4Route: &hasDefaultRoute, |
| } |
| } |
| } |
| |
| // onDefaultIPv6RouteChangeLocked must be called with the `ifState.mu` of the |
| // interface identified by `nicid` and the route table locked (in that order) |
| // to avoid races against other route changes. |
| func (ns *Netstack) onDefaultIPv6RouteChangeLocked(nicid tcpip.NICID, hasDefaultRoute bool) { |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &defaultRouteChanged{ |
| nicid: nicid, |
| hasDefaultIPv6Route: &hasDefaultRoute, |
| } |
| } |
| } |
| |
| // onDefaultRouteChangeLocked must be called with the `ifState.mu` of the |
| // interface identified by `nicid` and the route table locked (in that order) |
| // to avoid races against other route changes. |
| func (ns *Netstack) onDefaultRouteChangeLocked(nicid tcpip.NICID, hasDefaultIPv4Route bool, hasDefaultIPv6Route bool) { |
| if ns.interfaceEventChan != nil { |
| ns.interfaceEventChan <- &defaultRouteChanged{ |
| nicid: nicid, |
| hasDefaultIPv4Route: &hasDefaultIPv4Route, |
| hasDefaultIPv6Route: &hasDefaultIPv6Route, |
| } |
| } |
| } |
| |
| func (ifs *ifState) dhcpLostLocked(lost tcpip.AddressWithPrefix) { |
| name := ifs.ns.name(ifs.nicid) |
| |
| switch status := ifs.removeAddress(tcpip.ProtocolAddress{ |
| AddressWithPrefix: lost, |
| Protocol: header.IPv4ProtocolNumber, |
| }); status { |
| case zx.ErrOk: |
| _ = syslog.Infof("NIC %s: removed DHCP address %s", name, lost) |
| case zx.ErrNotFound: |
| _ = syslog.Warnf("NIC %s: DHCP address %s to be removed not found", name, lost) |
| case zx.ErrBadState: |
| _ = syslog.Warnf("NIC %s: NIC not found when removing DHCP address %s", name, lost) |
| default: |
| panic(fmt.Sprintf("NIC %s: unexpected error removing DHCP address %s: %s", name, lost, status)) |
| } |
| |
| // Remove the dynamic routes for this interface. |
| ifs.ns.UpdateRoutesByInterfaceLocked(ifs.nicid, routetypes.ActionDeleteDynamic) |
| } |
| |
| func (ifs *ifState) dhcpAcquired(ctx context.Context, lost, acquired tcpip.AddressWithPrefix, config dhcp.Config) { |
| select { |
| case <-ctx.Done(): |
| return |
| case ifs.dhcpLock <- struct{}{}: |
| defer func() { |
| _ = <-ifs.dhcpLock |
| }() |
| } |
| |
| name := ifs.ns.name(ifs.nicid) |
| |
| if lost == acquired { |
| _ = syslog.Infof("NIC %s: DHCP renewed address %s for %s", name, acquired, config.LeaseLength) |
| |
| ifs.ns.stack.SetAddressLifetimes(ifs.nicid, acquired.Address, stack.AddressLifetimes{ |
| Deprecated: false, |
| PreferredUntil: tcpip.MonotonicTime{}.Add(time.Duration(math.MaxInt64)), |
| ValidUntil: tcpip.MonotonicTime{}.Add(time.Duration(config.UpdatedAt.Add(config.LeaseLength.Duration()).MonotonicNano())), |
| }) |
| } else { |
| if lost != (tcpip.AddressWithPrefix{}) { |
| ifs.dhcpLostLocked(lost) |
| } |
| |
| if acquired != (tcpip.AddressWithPrefix{}) { |
| if ok, reason := ifs.addAddress(tcpip.ProtocolAddress{ |
| Protocol: ipv4.ProtocolNumber, |
| AddressWithPrefix: acquired, |
| }, stack.AddressProperties{ |
| Lifetimes: stack.AddressLifetimes{ |
| PreferredUntil: tcpip.MonotonicTime{}.Add(time.Duration(math.MaxInt64)), |
| ValidUntil: tcpip.MonotonicTime{}.Add(time.Duration(config.UpdatedAt.Add(config.LeaseLength.Duration()).MonotonicNano())), |
| }, |
| }); !ok { |
| _ = syslog.Errorf("NIC %s: failed to add DHCP acquired address %s: %s", name, acquired, reason) |
| } else { |
| _ = syslog.Infof("NIC %s: DHCP acquired address %s for %s", name, acquired, config.LeaseLength) |
| |
| // Add a route for the local subnet. |
| rs := []tcpip.Route{ |
| addressWithPrefixRoute(ifs.nicid, acquired), |
| } |
| // Add a default route through each router. |
| for _, router := range config.Router { |
| // Reject non-unicast addresses to avoid an explosion of traffic in |
| // case of misconfiguration. |
| if ip := net.IP(router.AsSlice()); !ip.IsLinkLocalUnicast() && !ip.IsGlobalUnicast() { |
| _ = syslog.Warnf("NIC %s: DHCP specified non-unicast router %s, skipping", name, ip) |
| continue |
| } |
| rs = append(rs, defaultV4Route(ifs.nicid, router)) |
| } |
| _ = syslog.Infof("adding routes %s with metric=<not-set> dynamic=true", rs) |
| |
| ifs.mu.Lock() |
| ifs.addRoutesWithPreferenceLocked(rs, routetypes.MediumPreference, nil /* metric */, true /* dynamic */, true /* overwriteIfAlreadyExist */, routetypes.GlobalRouteSet()) |
| ifs.mu.Unlock() |
| } |
| } |
| } |
| |
| if updated := ifs.setDNSServers(config.DNS); updated { |
| _ = syslog.Infof("NIC %s: set DNS servers: %s", name, config.DNS) |
| } |
| } |
| |
| // setDNSServers updates the receiver's dnsServers if necessary and returns |
| // whether they were updated. |
| func (ifs *ifState) setDNSServers(servers []tcpip.Address) bool { |
| ifs.dns.mu.Lock() |
| sameDNS := len(ifs.dns.mu.servers) == len(servers) |
| if sameDNS { |
| for i := range ifs.dns.mu.servers { |
| sameDNS = ifs.dns.mu.servers[i] == servers[i] |
| if !sameDNS { |
| break |
| } |
| } |
| } |
| if !sameDNS { |
| ifs.dns.mu.servers = servers |
| ifs.ns.dnsConfig.UpdateDhcpServers(ifs.nicid, &ifs.dns.mu.servers) |
| } |
| ifs.dns.mu.Unlock() |
| return !sameDNS |
| } |
| |
| // setDHCPStatusLocked updates the DHCP status on an interface and runs the DHCP |
| // client if it should be enabled. |
| // |
| // Precondition: ifs.mu and ifs.dhcpLock is held. |
| func (ifs *ifState) setDHCPStatusLocked(name string, enabled bool) { |
| ifs.mu.dhcp.enabled = enabled |
| ifs.mu.dhcp.cancelLocked() |
| if ifs.mu.dhcp.enabled && ifs.IsUpLocked() { |
| ifs.runDHCPLocked(name) |
| } |
| } |
| |
| // setDHCPStatus updates the DHCP status on an interface and runs the DHCP |
| // client if it should be enabled. |
| // |
| // Takes the ifState lock. |
| func (ifs *ifState) setDHCPStatus(name string, enabled bool) { |
| _ = syslog.VLogf(syslog.DebugVerbosity, "NIC %s: setDHCPStatus = %t", name, enabled) |
| ifs.dhcpLock <- struct{}{} |
| ifs.mu.Lock() |
| defer func() { |
| ifs.mu.Unlock() |
| <-ifs.dhcpLock |
| }() |
| ifs.setDHCPStatusLocked(name, enabled) |
| } |
| |
| // Runs the DHCP client with a fresh context and initializes ifs.mu.dhcp.cancel. |
| // Call the old cancel function before calling this function. |
| func (ifs *ifState) runDHCPLocked(name string) { |
| _ = syslog.Infof("NIC %s: run DHCP", name) |
| ctx, cancel := context.WithCancel(context.Background()) |
| |
| completeCh := make(chan tcpip.AddressWithPrefix) |
| ifs.mu.dhcp.cancelLocked = func() { |
| cancel() |
| if addr := <-completeCh; addr != (tcpip.AddressWithPrefix{}) { |
| // Remove this address and cleanup routes. |
| ifs.dhcpLostLocked(addr) |
| } |
| } |
| ifs.mu.dhcp.running = func() bool { |
| return ctx.Err() == nil |
| } |
| if c := ifs.mu.dhcp.Client; c != nil { |
| go func() { |
| defer cancel() |
| completeCh <- c.Run(ctx) |
| close(completeCh) |
| }() |
| } else { |
| cancel() |
| panic(fmt.Sprintf("nil DHCP client on interface %s", name)) |
| } |
| } |
| |
| func (ifs *ifState) dhcpEnabled() bool { |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| return ifs.mu.dhcp.enabled |
| } |
| |
| func (ifs *ifState) onDownLocked(name string, closed bool) { |
| // Stop DHCP, this triggers the removal of all dynamically obtained configuration (IP, routes, |
| // DNS servers). |
| ifs.mu.dhcp.cancelLocked() |
| |
| // Remove DNS servers through ifs. |
| ifs.ns.dnsConfig.RemoveAllServersWithNIC(ifs.nicid) |
| ifs.setDNSServers(nil) |
| |
| if closed { |
| // The interface is removed, force all of its routes to be removed. |
| ifs.ns.UpdateRoutesByInterfaceLocked(ifs.nicid, routetypes.ActionDeleteAll) |
| } else { |
| // The interface is down, disable static routes (dynamic ones are handled |
| // by the cancelled DHCP server). |
| ifs.ns.UpdateRoutesByInterfaceLocked(ifs.nicid, routetypes.ActionDisableStatic) |
| } |
| |
| _ = ifs.ns.delRouteLocked(ipv6LinkLocalOnLinkRoute(ifs.nicid), routetypes.GlobalRouteSet()) |
| |
| if closed { |
| switch err := ifs.ns.stack.RemoveNIC(ifs.nicid); err.(type) { |
| case nil: |
| ifs.ns.resetDestinationCache() |
| case *tcpip.ErrUnknownNICID: |
| default: |
| _ = syslog.Warnf("error removing NIC %s in stack.Stack: %s", name, err) |
| } |
| |
| for _, h := range ifs.ns.nicRemovedHandlers { |
| h.RemovedNIC(ifs.nicid) |
| } |
| // TODO(https://fxbug.dev/42167700): Re-enabling bridged interfaces on removal |
| // of the bridge is a hack, and needs a proper implementation. |
| for _, nicid := range ifs.bridgedInterfaces { |
| nicInfo, ok := ifs.ns.stack.NICInfo()[nicid] |
| if !ok { |
| continue |
| } |
| |
| bridgedIfs := nicInfo.Context.(*ifState) |
| bridgedIfs.mu.Lock() |
| if bridgedIfs.IsUpLocked() { |
| switch err := ifs.ns.stack.EnableNIC(nicid); err.(type) { |
| case nil, *tcpip.ErrUnknownNICID: |
| default: |
| _ = syslog.Errorf("failed to enable bridged interface %d after removing bridge: %s", nicid, err) |
| } |
| } |
| bridgedIfs.mu.Unlock() |
| } |
| |
| if err := ifs.authorizationToken.Close(); err != nil { |
| _ = syslog.Warnf("error closing authorization token for NIC %s: %s", name, err) |
| } |
| |
| ifs.ns.onInterfaceRemoveLocked(ifs.nicid) |
| } else { |
| if err := ifs.ns.stack.DisableNIC(ifs.nicid); err != nil { |
| _ = syslog.Errorf("error disabling NIC %s in stack.Stack: %s", name, err) |
| } |
| } |
| } |
| |
| func (ifs *ifState) stateChangeLocked(name string, adminUp, linkOnline bool) bool { |
| before := ifs.IsUpLocked() |
| after := adminUp && linkOnline |
| |
| if after != before { |
| if after { |
| if ifs.bridgeable.IsBridged() { |
| _ = syslog.Warnf("not enabling NIC %s in stack.Stack because it is attached to a bridge", name) |
| } else if err := ifs.ns.stack.EnableNIC(ifs.nicid); err != nil { |
| _ = syslog.Errorf("error enabling NIC %s in stack.Stack: %s", name, err) |
| } |
| |
| // Re-enable static routes out this interface. |
| ifs.ns.UpdateRoutesByInterfaceLocked(ifs.nicid, routetypes.ActionEnableStatic) |
| if ifs.mu.dhcp.enabled { |
| ifs.mu.dhcp.cancelLocked() |
| ifs.runDHCPLocked(name) |
| } |
| if ifs.endpoint.Capabilities()&stack.CapabilityLoopback == 0 { |
| // Add an on-link route for the IPv6 link-local subnet to |
| // non-loopback interfaces. The route is added as a 'static' |
| // route because Netstack will remove dynamic routes on DHCPv4 |
| // changes. See staticRouteAvoidingLifeCycleHooks for more |
| // details. |
| ifs.ns.routeTable.AddRoute( |
| ipv6LinkLocalOnLinkRoute(ifs.nicid), |
| routetypes.MediumPreference, |
| ifs.metric, |
| true, /* metricTracksInterface */ |
| staticRouteAvoidingLifeCycleHooks, |
| true, /* enabled */ |
| routetypes.GlobalRouteSet(), |
| ) |
| } |
| ifs.ns.routeTable.UpdateStack(ifs.ns.stack, ifs.ns.resetDestinationCache) |
| } else { |
| ifs.onDownLocked(name, false /* closed */) |
| } |
| } |
| |
| ifs.mu.adminUp = adminUp |
| ifs.mu.linkOnline = linkOnline |
| |
| return after != before |
| } |
| |
| func (ifs *ifState) onLinkOnlineChanged(linkOnline bool) { |
| name := ifs.ns.name(ifs.nicid) |
| |
| ifs.dhcpLock <- struct{}{} |
| ifs.mu.Lock() |
| defer func() { |
| ifs.mu.Unlock() |
| <-ifs.dhcpLock |
| }() |
| |
| changed := ifs.stateChangeLocked(name, ifs.mu.adminUp, linkOnline) |
| _ = syslog.Infof("NIC %s: observed linkOnline=%t when adminUp=%t, interfacesChanged=%t", name, linkOnline, ifs.mu.adminUp, changed) |
| if changed { |
| ifs.ns.onOnlineChangeLocked(ifs.nicid, ifs.IsUpLocked()) |
| } |
| } |
| |
| func (ifs *ifState) setState(enabled bool) (bool, error) { |
| name := ifs.ns.name(ifs.nicid) |
| |
| wasEnabled, err := func() (bool, error) { |
| ifs.dhcpLock <- struct{}{} |
| ifs.mu.Lock() |
| defer func() { |
| ifs.mu.Unlock() |
| <-ifs.dhcpLock |
| }() |
| |
| wasEnabled := ifs.mu.adminUp |
| if wasEnabled == enabled { |
| return wasEnabled, nil |
| } |
| |
| if controller := ifs.controller; controller != nil { |
| fn := controller.Down |
| if enabled { |
| fn = controller.Up |
| } |
| if err := fn(); err != nil { |
| return wasEnabled, err |
| } |
| } |
| |
| changed := ifs.stateChangeLocked(name, enabled, ifs.LinkOnlineLocked()) |
| _ = syslog.Infof("NIC %s: set adminUp=%t when linkOnline=%t, interfacesChanged=%t", name, ifs.mu.adminUp, ifs.LinkOnlineLocked(), changed) |
| |
| if changed { |
| ifs.ns.onOnlineChangeLocked(ifs.nicid, ifs.IsUpLocked()) |
| } |
| |
| return wasEnabled, nil |
| }() |
| if err != nil { |
| _ = syslog.Infof("NIC %s: setting adminUp=%t failed: %s", name, enabled, err) |
| return wasEnabled, err |
| } |
| |
| return wasEnabled, nil |
| } |
| |
| func (ifs *ifState) Up() error { |
| _, err := ifs.setState(true) |
| return err |
| } |
| |
| func (ifs *ifState) Down() error { |
| _, err := ifs.setState(false) |
| return err |
| } |
| |
| func (ifs *ifState) RemoveByUser() { |
| ifs.remove(admin.InterfaceRemovedReasonUser) |
| } |
| |
| func (ifs *ifState) RemoveByLinkClose() { |
| ifs.remove(admin.InterfaceRemovedReasonPortClosed) |
| } |
| |
| func (ifs *ifState) remove(reason admin.InterfaceRemovedReason) { |
| // Cannot hold `ifs.mu` across detaching and waiting for endpoint cleanup as |
| // link status changes blocks endpoint cleanup and will attempt to acquire |
| // `ifs.mu`. |
| if func() bool { |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| removed := ifs.mu.removed |
| ifs.mu.removed = true |
| return removed |
| }() { |
| return |
| } |
| |
| name := ifs.ns.name(ifs.nicid) |
| |
| _ = syslog.Infof("NIC %s: removing, reason=%s", name, reason) |
| |
| // Close all open control channels with the interface before removing it from |
| // the stack. That prevents any further administrative action from happening. |
| pendingNotification := ifs.adminControls.stopServing() |
| // Detach the endpoint and wait for clean termination before we remove the |
| // NIC from the stack, that ensures that we can't be racing with other calls |
| // to onDown that are signalling link status changes. |
| ifs.endpoint.Attach(nil) |
| _ = syslog.Infof("NIC %s: waiting for endpoint cleanup...", name) |
| ifs.endpoint.Wait() |
| _ = syslog.Infof("NIC %s: endpoint cleanup done", name) |
| |
| ifs.dhcpLock <- struct{}{} |
| ifs.mu.Lock() |
| defer func() { |
| ifs.mu.Unlock() |
| <-ifs.dhcpLock |
| }() |
| |
| ifs.onDownLocked(name, true /* closed */) |
| |
| sendControlTerminationReason(pendingNotification, reason) |
| _ = syslog.Infof("NIC %s: removed", name) |
| } |
| |
| var nameProviderErrorLogged uint32 = 0 |
| |
| // TODO(stijlist): figure out a way to make it impossible to accidentally |
| // enable DHCP on loopback interfaces. |
| func (ns *Netstack) addLoopback() error { |
| ifs, err := ns.addEndpoint( |
| func(tcpip.NICID) string { |
| return "lo" |
| }, |
| // To match linux behaviour, as per |
| // https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/net/loopback.c#L162. |
| ethernet.New(loopback.New()), |
| nil, /* controller */ |
| nil, /* observer */ |
| defaultInterfaceMetric, |
| qdiscConfig{}, |
| ) |
| if err != nil { |
| return err |
| } |
| |
| ifs.mu.Lock() |
| nicid := ifs.nicid |
| ifs.mu.Unlock() |
| |
| // Loopback interfaces do not need NDP or DAD. |
| if err := func() tcpip.Error { |
| ep, err := ns.stack.GetNetworkEndpoint(nicid, ipv6.ProtocolNumber) |
| if err != nil { |
| return err |
| } |
| |
| // Must never fail, but the compiler can't tell. |
| ndpEP := ep.(ipv6.NDPEndpoint) |
| ndpEP.SetNDPConfigurations(ipv6.NDPConfigurations{}) |
| |
| dadEP := ep.(stack.DuplicateAddressDetector) |
| dadEP.SetDADConfigurations(stack.DADConfigurations{}) |
| |
| return nil |
| }(); err != nil { |
| return fmt.Errorf("error setting NDP configurations to NIC ID %d: %s", nicid, err) |
| } |
| |
| ipv4LoopbackPrefix := tcpip.MaskFromBytes(net.IP(ipv4Loopback.AsSlice()).DefaultMask()).Prefix() |
| ipv4LoopbackProtocolAddress := tcpip.ProtocolAddress{ |
| Protocol: ipv4.ProtocolNumber, |
| AddressWithPrefix: tcpip.AddressWithPrefix{ |
| Address: ipv4Loopback, |
| PrefixLen: ipv4LoopbackPrefix, |
| }, |
| } |
| ipv4LoopbackRoute := addressWithPrefixRoute(nicid, ipv4LoopbackProtocolAddress.AddressWithPrefix) |
| if ok, reason := ifs.addAddress(ipv4LoopbackProtocolAddress, stack.AddressProperties{}); !ok { |
| return fmt.Errorf("ifs.addAddress(%d, %s): %s", nicid, ipv4LoopbackProtocolAddress.AddressWithPrefix, reason) |
| } |
| |
| ipv6LoopbackProtocolAddress := tcpip.ProtocolAddress{ |
| Protocol: ipv6.ProtocolNumber, |
| AddressWithPrefix: ipv6Loopback.WithPrefix(), |
| } |
| if ok, reason := ifs.addAddress(ipv6LoopbackProtocolAddress, stack.AddressProperties{}); !ok { |
| return fmt.Errorf("ifs.addAddress(%d, %s): %s", nicid, ipv6LoopbackProtocolAddress.AddressWithPrefix, reason) |
| } |
| |
| ifs.mu.Lock() |
| ifs.addRoutesWithPreferenceLocked( |
| []tcpip.Route{ |
| ipv4LoopbackRoute, |
| { |
| Destination: util.PointSubnet(ipv6Loopback), |
| NIC: nicid, |
| }, |
| }, |
| routetypes.MediumPreference, |
| nil, /* metric */ |
| false, /* dynamic */ |
| true, /* overwriteIfAlreadyExist */ |
| routetypes.GlobalRouteSet(), |
| ) |
| ifs.mu.Unlock() |
| |
| if err := ifs.Up(); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (ns *Netstack) Bridge(nics []tcpip.NICID) (*ifState, error) { |
| links := make([]*bridge.BridgeableEndpoint, 0, len(nics)) |
| ifStates := make([]*ifState, 0, len(nics)) |
| metric := defaultInterfaceMetric |
| for _, nicid := range nics { |
| nicInfo, ok := ns.stack.NICInfo()[nicid] |
| if !ok { |
| return nil, fmt.Errorf("failed to find NIC %d", nicid) |
| } |
| ifs := nicInfo.Context.(*ifState) |
| ifStates = append(ifStates, ifs) |
| |
| if controller := ifs.controller; controller != nil { |
| if err := controller.SetPromiscuousMode(true); err != nil { |
| return nil, fmt.Errorf("error enabling promiscuous mode for NIC %d in stack.Stack while bridging endpoint: %w", ifs.nicid, err) |
| } |
| } |
| links = append(links, ifs.bridgeable) |
| |
| // TODO(https://fxbug.dev/42167696): Replace this with explicit |
| // configuration. For now, take the minimum default route |
| // metric across all the links because there is currently |
| // no way to specify it when creating the bridge. |
| if ifs.metric < metric { |
| metric = ifs.metric |
| } |
| } |
| |
| b, err := bridge.New(links) |
| if err != nil { |
| return nil, err |
| } |
| |
| ifs, err := ns.addEndpoint( |
| func(nicid tcpip.NICID) string { |
| return fmt.Sprintf("br%d", nicid) |
| }, |
| b, |
| b, |
| nil, /* observer */ |
| metric, |
| qdiscConfig{}, |
| ) |
| if err != nil { |
| return nil, err |
| } |
| |
| ifs.bridgedInterfaces = nics |
| |
| for _, ifs := range ifStates { |
| func() { |
| // Disabling the NIC and attaching interfaces to the bridge must be called |
| // under lock to avoid racing against admin/link status changes which may |
| // enable the NIC. |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| // TODO(https://fxbug.dev/42167700): Disabling bridged interfaces inside gVisor |
| // is a hack, and in need of a proper implementation. |
| switch err := ifs.ns.stack.DisableNIC(ifs.nicid); err.(type) { |
| case nil: |
| case *tcpip.ErrUnknownNICID: |
| // TODO(https://fxbug.dev/42168026): Handle bridged interface removal. |
| _ = syslog.Warnf("NIC %d removed while attaching to bridge", ifs.nicid) |
| default: |
| panic(fmt.Sprintf("unexpected error disabling NIC %d while attaching to bridge: %s", ifs.nicid, err)) |
| } |
| |
| ifs.bridgeable.SetBridge(b) |
| }() |
| } |
| return ifs, err |
| } |
| |
| func makeEndpointName(prefix, configName string) func(nicid tcpip.NICID) string { |
| return func(nicid tcpip.NICID) string { |
| if len(configName) == 0 { |
| return fmt.Sprintf("%s%d", prefix, nicid) |
| } |
| return configName |
| } |
| } |
| |
| type qdiscConfig struct { |
| numQueues int |
| queueLen int |
| } |
| |
| // addEndpoint creates a new NIC with stack.Stack. |
| func (ns *Netstack) addEndpoint( |
| nameFn func(nicid tcpip.NICID) string, |
| ep stack.LinkEndpoint, |
| controller link.Controller, |
| observer link.Observer, |
| metric routetypes.Metric, |
| qdisc qdiscConfig, |
| ) (*ifState, error) { |
| ifs := &ifState{ |
| ns: ns, |
| controller: controller, |
| observer: observer, |
| metric: metric, |
| } |
| ifs.dhcpLock = make(chan struct{}, 1) |
| ifs.adminControls.mu.controls = make(map[*adminControlImpl]struct{}) |
| if observer != nil { |
| observer.SetOnLinkClosed(ifs.RemoveByLinkClose) |
| observer.SetOnLinkOnlineChanged(ifs.onLinkOnlineChanged) |
| } |
| |
| ifs.mu.dhcp.running = func() bool { return false } |
| ifs.mu.dhcp.cancelLocked = func() {} |
| |
| ns.mu.Lock() |
| ifs.nicid = ns.mu.countNIC + 1 |
| ns.mu.countNIC++ |
| ns.mu.Unlock() |
| name := nameFn(ifs.nicid) |
| |
| token, err := zx.NewEvent(0 /* options */) |
| if err != nil { |
| return nil, fmt.Errorf("while allocating authorization token for %s: %w", name, err) |
| } |
| ifs.authorizationToken = token |
| |
| // LinkEndpoint chains: |
| // Put sniffer as close as the NIC. |
| // A wrapper LinkEndpoint should encapsulate the underlying |
| // one, and manifest itself to 3rd party netstack. |
| ifs.bridgeable = bridge.NewEndpoint(sniffer.NewWithPrefix(packetsocket.New(ep), fmt.Sprintf("[%s(id=%d)] ", name, ifs.nicid))) |
| ep = ifs.bridgeable |
| ifs.endpoint = ep |
| |
| ifs.mu.Lock() |
| defer ifs.mu.Unlock() |
| |
| nicOpts := stack.NICOptions{Name: name, Context: ifs, Disabled: true} |
| if qdisc.numQueues > 0 { |
| if qdisc.queueLen == 0 { |
| panic("attempted to configure qdisc with zero-length queue") |
| } |
| nicOpts.QDisc = fifo.New(ep, qdisc.numQueues, qdisc.queueLen) |
| } |
| if err := ns.stack.CreateNICWithOptions(ifs.nicid, ep, nicOpts); err != nil { |
| // Clean up memory held by qdisc. |
| if nicOpts.QDisc != nil { |
| nicOpts.QDisc.Close() |
| } |
| return nil, fmt.Errorf("NIC %s: could not create NIC: %w", name, WrapTcpIpError(err)) |
| } |
| |
| _ = syslog.Infof("NIC %s added as nicID (%d)", name, ifs.nicid) |
| |
| if linkAddr := ep.LinkAddress(); len(linkAddr) > 0 { |
| dhcpClient := dhcp.NewClient(ns.stack, ifs.nicid, linkAddr, dhcpAcquisition, dhcpBackoff, dhcpRetransmission, ifs.dhcpAcquired) |
| ifs.mu.dhcp.Client = dhcpClient |
| } |
| |
| ns.onInterfaceAddLocked(ifs, name) |
| |
| if ifs.endpoint.Capabilities()&stack.CapabilityLoopback == 0 { |
| // Add initial routes to the IPv4 and IPv6 Multicast subnets. Note that |
| // these aren't strictly required to route multicast traffic bound to a |
| // particular interface (since gVisor has special-case logic in the |
| // forwarding pipeline to handle such traffic), but they are needed to |
| // forward multicast traffic when the interface is unspecified. |
| // They are also installed in Netstack3, so adding them here improves |
| // consistency between the two stacks. |
| ifs.ns.routeTable.AddRoute( |
| tcpip.Route{Destination: ipv4MulticastSubnet().Subnet(), NIC: ifs.nicid}, |
| routetypes.MediumPreference, |
| ifs.metric, |
| true, /* metricTracksInterface */ |
| false, /* dynamic */ |
| true, /* enabled */ |
| routetypes.GlobalRouteSet(), |
| ) |
| ifs.ns.routeTable.AddRoute( |
| tcpip.Route{Destination: ipv6MulticastSubnet().Subnet(), NIC: ifs.nicid}, |
| routetypes.MediumPreference, |
| ifs.metric, |
| true, /* metricTracksInterface */ |
| false, /* dynamic */ |
| true, /* enabled */ |
| routetypes.GlobalRouteSet(), |
| ) |
| ifs.ns.routeTable.UpdateStack(ifs.ns.stack, ifs.ns.resetDestinationCache) |
| } |
| |
| return ifs, nil |
| } |
| |
| func (ns *Netstack) getIfStateInfo(nicInfo map[tcpip.NICID]stack.NICInfo) map[tcpip.NICID]ifStateInfo { |
| ifStates := make(map[tcpip.NICID]ifStateInfo) |
| for id, ni := range nicInfo { |
| ifs := ni.Context.(*ifState) |
| |
| ifs.dns.mu.Lock() |
| dnsServers := ifs.dns.mu.servers |
| ifs.dns.mu.Unlock() |
| |
| ifs.mu.Lock() |
| info := ifStateInfo{ |
| NICInfo: ni, |
| nicid: ifs.nicid, |
| adminUp: ifs.mu.adminUp, |
| linkOnline: ifs.LinkOnlineLocked(), |
| dnsServers: dnsServers, |
| dhcpEnabled: ifs.mu.dhcp.enabled, |
| } |
| if ifs.mu.dhcp.enabled { |
| info.dhcpInfo = ifs.mu.dhcp.Info() |
| info.dhcpStats = ifs.mu.dhcp.Stats() |
| info.dhcpStateRecentHistory = ifs.mu.dhcp.StateRecentHistory() |
| } |
| |
| for _, network := range []tcpip.NetworkProtocolNumber{header.IPv4ProtocolNumber, header.IPv6ProtocolNumber} { |
| { |
| neighbors, err := ns.stack.Neighbors(id, network) |
| switch err.(type) { |
| case nil: |
| if info.neighbors == nil { |
| info.neighbors = make(map[string]stack.NeighborEntry) |
| } |
| for _, n := range neighbors { |
| info.neighbors[n.Addr.String()] = n |
| } |
| case *tcpip.ErrNotSupported: |
| // NIC does not have a neighbor table, skip. |
| case *tcpip.ErrUnknownNICID: |
| _ = syslog.Warnf("getIfStateInfo: NIC removed before ns.stack.Neighbors(%d) could be called", id) |
| default: |
| _ = syslog.Errorf("getIfStateInfo: unexpected error from ns.stack.Neighbors(%d) = %s", id, err) |
| } |
| } |
| } |
| |
| info.networkEndpointStats = make(map[string]stack.NetworkEndpointStats, len(ni.NetworkStats)) |
| for proto, netEPStats := range ni.NetworkStats { |
| info.networkEndpointStats[networkProtocolToString(proto)] = netEPStats |
| } |
| |
| ifs.mu.Unlock() |
| info.controller = ifs.controller |
| ifStates[id] = info |
| } |
| return ifStates |
| } |
| |
| func networkProtocolToString(proto tcpip.NetworkProtocolNumber) string { |
| switch proto { |
| case header.IPv4ProtocolNumber: |
| return "IPv4" |
| case header.IPv6ProtocolNumber: |
| return "IPv6" |
| case header.ARPProtocolNumber: |
| return "ARP" |
| default: |
| return fmt.Sprintf("0x%x", proto) |
| } |
| } |
| |
| type filterNicInfoProvider struct { |
| stack *stack.Stack |
| } |
| |
| var _ filter.NicInfoProvider = (*filterNicInfoProvider)(nil) |
| |
| // GetNicInfo implements filter.NicInfoProvider. |
| func (p *filterNicInfoProvider) GetNicInfo(nicid tcpip.NICID) (string, *network.DeviceClass) { |
| nicInfo, ok := p.stack.NICInfo()[nicid] |
| if !ok { |
| return "", nil |
| } |
| ifs := nicInfo.Context.(*ifState) |
| if ifs.controller == nil { |
| return nicInfo.Name, nil |
| } |
| deviceClass := ifs.controller.DeviceClass() |
| return nicInfo.Name, &deviceClass |
| } |