| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package netstack |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "sort" |
| "sync" |
| "syscall/zx/fidl" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv" |
| "go.fuchsia.dev/fuchsia/src/lib/component" |
| syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go" |
| |
| "fidl/fuchsia/hardware/network" |
| "fidl/fuchsia/net" |
| "fidl/fuchsia/net/interfaces" |
| |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| tcpipstack "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| func interfaceProperties(nicInfo tcpipstack.NICInfo, hasDefaultIPv4Route, hasDefaultIPv6Route bool) interfaces.Properties { |
| var p interfaces.Properties |
| ifs := nicInfo.Context.(*ifState) |
| p.SetId(uint64(ifs.nicid)) |
| p.SetName(nicInfo.Name) |
| p.SetHasDefaultIpv4Route(hasDefaultIPv4Route) |
| p.SetHasDefaultIpv6Route(hasDefaultIPv6Route) |
| |
| if ifs.endpoint.Capabilities()&tcpipstack.CapabilityLoopback != 0 { |
| p.SetDeviceClass(interfaces.DeviceClassWithLoopback(interfaces.Empty{})) |
| } else if ifs.controller != nil { |
| p.SetDeviceClass(interfaces.DeviceClassWithDevice(ifs.controller.DeviceClass())) |
| } else { |
| p.SetDeviceClass(interfaces.DeviceClassWithDevice(network.DeviceClassUnknown)) |
| } |
| |
| ifs.mu.Lock() |
| p.SetOnline(ifs.IsUpLocked()) |
| ifs.mu.Unlock() |
| |
| var addrs []interfaces.Address |
| for _, a := range nicInfo.ProtocolAddresses { |
| if a.Protocol != ipv4.ProtocolNumber && a.Protocol != ipv6.ProtocolNumber { |
| continue |
| } |
| var addr interfaces.Address |
| addr.SetAddr(net.Subnet{ |
| Addr: fidlconv.ToNetIpAddress(a.AddressWithPrefix.Address), |
| PrefixLen: uint8(a.AddressWithPrefix.PrefixLen), |
| }) |
| addrs = append(addrs, addr) |
| } |
| sort.Slice(addrs, func(i, j int) bool { |
| return cmpSubnet(addrs[i].GetAddr(), addrs[j].GetAddr()) <= 0 |
| }) |
| p.SetAddresses(addrs) |
| |
| return p |
| } |
| |
| var _ interfaces.WatcherWithCtx = (*interfaceWatcherImpl)(nil) |
| |
| type interfaceWatcherImpl struct { |
| cancelServe context.CancelFunc |
| ready chan struct{} |
| mu struct { |
| sync.Mutex |
| isHanging bool |
| queue []interfaces.Event |
| } |
| } |
| |
| const maxInterfaceWatcherQueueLen = 512 |
| |
| func (wi *interfaceWatcherImpl) onEvent(e interfaces.Event) { |
| wi.mu.Lock() |
| if len(wi.mu.queue) >= maxInterfaceWatcherQueueLen { |
| wi.cancelServe() |
| } else { |
| wi.mu.queue = append(wi.mu.queue, e) |
| } |
| queueLen := len(wi.mu.queue) |
| isHanging := wi.mu.isHanging |
| wi.mu.Unlock() |
| |
| if queueLen > 0 && isHanging { |
| select { |
| case wi.ready <- struct{}{}: |
| default: |
| } |
| } |
| } |
| |
| func cmpSubnet(s1 net.Subnet, s2 net.Subnet) int { |
| switch s1.Addr.Which() { |
| case net.IpAddressIpv4: |
| if s2.Addr.Which() == net.IpAddressIpv6 { |
| return -1 |
| } |
| if diff := bytes.Compare(s1.Addr.Ipv4.Addr[:], s2.Addr.Ipv4.Addr[:]); diff != 0 { |
| return diff |
| } |
| case net.IpAddressIpv6: |
| if s2.Addr.Which() == net.IpAddressIpv4 { |
| return 1 |
| } |
| if diff := bytes.Compare(s1.Addr.Ipv6.Addr[:], s2.Addr.Ipv6.Addr[:]); diff != 0 { |
| return diff |
| } |
| } |
| if s1.PrefixLen < s2.PrefixLen { |
| return -1 |
| } else if s1.PrefixLen > s2.PrefixLen { |
| return 1 |
| } |
| return 0 |
| } |
| |
| func emptyInterfaceProperties(p interfaces.Properties) bool { |
| return !(p.HasId() || p.HasAddresses() || p.HasOnline() || p.HasDeviceClass() || p.HasHasDefaultIpv4Route() || p.HasHasDefaultIpv6Route()) |
| } |
| |
| // Diff two interface properties. The return value will have no fields present |
| // if there is no difference. The return value may contain references to fields |
| // in p2. |
| func diffInterfaceProperties(p1, p2 interfaces.Properties) interfaces.Properties { |
| var diff interfaces.Properties |
| if p1.GetOnline() != p2.GetOnline() { |
| diff.SetOnline(p2.GetOnline()) |
| } |
| if p1.GetHasDefaultIpv4Route() != p2.GetHasDefaultIpv4Route() { |
| diff.SetHasDefaultIpv4Route(p2.GetHasDefaultIpv4Route()) |
| } |
| if p1.GetHasDefaultIpv6Route() != p2.GetHasDefaultIpv6Route() { |
| diff.SetHasDefaultIpv6Route(p2.GetHasDefaultIpv6Route()) |
| } |
| if func() bool { |
| if len(p2.GetAddresses()) != len(p1.GetAddresses()) { |
| return true |
| } |
| for i, addr := range p1.GetAddresses() { |
| if cmpSubnet(addr.GetAddr(), p2.GetAddresses()[i].GetAddr()) != 0 { |
| return true |
| } |
| } |
| return false |
| }() { |
| diff.SetAddresses(p2.GetAddresses()) |
| } |
| if !emptyInterfaceProperties(diff) { |
| diff.SetId(p2.GetId()) |
| } |
| return diff |
| } |
| |
| func (wi *interfaceWatcherImpl) Watch(ctx fidl.Context) (interfaces.Event, error) { |
| wi.mu.Lock() |
| defer wi.mu.Unlock() |
| |
| if wi.mu.isHanging { |
| wi.cancelServe() |
| return interfaces.Event{}, fmt.Errorf("not allowed to watch when a call is already in progress") |
| } |
| |
| for { |
| if len(wi.mu.queue) > 0 { |
| event := wi.mu.queue[0] |
| wi.mu.queue = wi.mu.queue[1:] |
| return event, nil |
| } |
| |
| wi.mu.isHanging = true |
| wi.mu.Unlock() |
| |
| var err error |
| select { |
| case <-wi.ready: |
| case <-ctx.Done(): |
| err = fmt.Errorf("cancelled: %s", ctx.Err()) |
| } |
| |
| wi.mu.Lock() |
| wi.mu.isHanging = false |
| if err != nil { |
| return interfaces.Event{}, err |
| } |
| } |
| } |
| |
| type interfaceWatcherCollection struct { |
| mu struct { |
| sync.Mutex |
| lastObserved map[tcpip.NICID]interfaces.Properties |
| watchers map[*interfaceWatcherImpl]struct{} |
| } |
| } |
| |
| func (ns *Netstack) onPropertiesChange(nicid tcpip.NICID) { |
| ns.interfaceWatchers.mu.Lock() |
| defer ns.interfaceWatchers.mu.Unlock() |
| |
| nicInfo, ok := ns.stack.NICInfo()[nicid] |
| if !ok { |
| syslog.WarnTf("fuchsia.net.interfaces/Watcher", "onPropertiesChange interface %d cannot be found", nicid) |
| return |
| } |
| |
| if properties, ok := ns.interfaceWatchers.mu.lastObserved[nicid]; ok { |
| newProperties := interfaceProperties(nicInfo, properties.GetHasDefaultIpv4Route(), properties.GetHasDefaultIpv6Route()) |
| if diff := diffInterfaceProperties(properties, newProperties); !emptyInterfaceProperties(diff) { |
| ns.interfaceWatchers.mu.lastObserved[nicid] = newProperties |
| for w := range ns.interfaceWatchers.mu.watchers { |
| w.onEvent(interfaces.EventWithChanged(diff)) |
| } |
| } |
| } else { |
| syslog.WarnTf("fuchsia.net.interfaces/Watcher", "onPropertiesChange called regarding unknown interface %d", nicid) |
| } |
| } |
| |
| func (ns *Netstack) onDefaultRouteChange() { |
| ns.interfaceWatchers.mu.Lock() |
| defer ns.interfaceWatchers.mu.Unlock() |
| |
| v4DefaultRoute := make(map[tcpip.NICID]struct{}) |
| v6DefaultRoute := make(map[tcpip.NICID]struct{}) |
| for _, er := range ns.GetExtendedRouteTable() { |
| if er.Enabled { |
| if er.Route.Destination.Equal(header.IPv4EmptySubnet) { |
| v4DefaultRoute[er.Route.NIC] = struct{}{} |
| } else if er.Route.Destination.Equal(header.IPv6EmptySubnet) { |
| v6DefaultRoute[er.Route.NIC] = struct{}{} |
| } |
| } |
| } |
| |
| for nicid, properties := range ns.interfaceWatchers.mu.lastObserved { |
| var diff interfaces.Properties |
| diff.SetId(uint64(nicid)) |
| if _, ok := v4DefaultRoute[nicid]; ok != properties.GetHasDefaultIpv4Route() { |
| properties.SetHasDefaultIpv4Route(ok) |
| diff.SetHasDefaultIpv4Route(ok) |
| } |
| if _, ok := v6DefaultRoute[nicid]; ok != properties.GetHasDefaultIpv6Route() { |
| properties.SetHasDefaultIpv6Route(ok) |
| diff.SetHasDefaultIpv6Route(ok) |
| } |
| if diff.HasHasDefaultIpv4Route() || diff.HasHasDefaultIpv6Route() { |
| ns.interfaceWatchers.mu.lastObserved[nicid] = properties |
| for w := range ns.interfaceWatchers.mu.watchers { |
| w.onEvent(interfaces.EventWithChanged(diff)) |
| } |
| } |
| } |
| } |
| |
| func (ns *Netstack) onInterfaceAdd(nicid tcpip.NICID) { |
| ns.interfaceWatchers.mu.Lock() |
| defer ns.interfaceWatchers.mu.Unlock() |
| |
| if properties, ok := ns.interfaceWatchers.mu.lastObserved[nicid]; ok { |
| syslog.WarnTf("fuchsia.net.interfaces/Watcher", "interface added but already known: %+v", properties) |
| return |
| } |
| |
| nicInfo, ok := ns.stack.NICInfo()[nicid] |
| if !ok { |
| syslog.WarnTf("fuchsia.net.interfaces/Watcher", "interface %d added but not in NICInfo map", nicid) |
| return |
| } |
| |
| var hasDefaultIpv4Route, hasDefaultIpv6Route bool |
| for _, er := range ns.GetExtendedRouteTable() { |
| if er.Enabled && er.Route.NIC == nicid { |
| hasDefaultIpv4Route = hasDefaultIpv4Route || er.Route.Destination.Equal(header.IPv4EmptySubnet) |
| hasDefaultIpv6Route = hasDefaultIpv6Route || er.Route.Destination.Equal(header.IPv6EmptySubnet) |
| } |
| } |
| |
| properties := interfaceProperties(nicInfo, hasDefaultIpv4Route, hasDefaultIpv6Route) |
| ns.interfaceWatchers.mu.lastObserved[nicid] = properties |
| for w := range ns.interfaceWatchers.mu.watchers { |
| w.onEvent(interfaces.EventWithAdded(properties)) |
| } |
| } |
| |
| func (c *interfaceWatcherCollection) onInterfaceRemove(nicid tcpip.NICID) { |
| c.mu.Lock() |
| defer c.mu.Unlock() |
| |
| if _, ok := c.mu.lastObserved[nicid]; !ok { |
| syslog.WarnTf("fuchsia.net.interfaces/Watcher", "unknown interface removed") |
| return |
| } |
| delete(c.mu.lastObserved, nicid) |
| for w := range c.mu.watchers { |
| w.onEvent(interfaces.EventWithRemoved(uint64(nicid))) |
| } |
| } |
| |
| var _ interfaces.StateWithCtx = (*interfaceStateImpl)(nil) |
| |
| type interfaceStateImpl struct { |
| ns *Netstack |
| } |
| |
| func (si *interfaceStateImpl) GetWatcher(_ fidl.Context, _ interfaces.WatcherOptions, watcher interfaces.WatcherWithCtxInterfaceRequest) error { |
| ctx, cancel := context.WithCancel(context.Background()) |
| impl := interfaceWatcherImpl{ |
| ready: make(chan struct{}, 1), |
| cancelServe: cancel, |
| } |
| impl.mu.queue = make([]interfaces.Event, 0, maxInterfaceWatcherQueueLen) |
| |
| si.ns.interfaceWatchers.mu.Lock() |
| |
| for _, properties := range si.ns.interfaceWatchers.mu.lastObserved { |
| impl.mu.queue = append(impl.mu.queue, interfaces.EventWithExisting(properties)) |
| } |
| impl.mu.queue = append(impl.mu.queue, interfaces.EventWithIdle(interfaces.Empty{})) |
| |
| si.ns.interfaceWatchers.mu.watchers[&impl] = struct{}{} |
| si.ns.interfaceWatchers.mu.Unlock() |
| |
| go func() { |
| component.ServeExclusive(ctx, &interfaces.WatcherWithCtxStub{Impl: &impl}, watcher.Channel, func(err error) { |
| _ = syslog.WarnTf("fuchsia.net.interfaces/Watcher", "%s", err) |
| }) |
| |
| si.ns.interfaceWatchers.mu.Lock() |
| delete(si.ns.interfaceWatchers.mu.watchers, &impl) |
| si.ns.interfaceWatchers.mu.Unlock() |
| }() |
| |
| return nil |
| } |