blob: 91372cec3779fd31704c8683a735130935e212fa [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//go:build !build_with_native_toolchain
// +build !build_with_native_toolchain
package netstack
import (
"bytes"
"context"
"errors"
"fmt"
"sort"
"sync"
"syscall/zx"
"syscall/zx/fidl"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"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/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"
)
const watcherProtocolName = "fuchsia.net.interfaces/Watcher"
// addressPatch is a patch to the address data exposed by upstream interface
// data. This type provides a mechanism by which clients of onPropertiesChange
// can extend the data exposed by fuchsia.net.interfaces beyond what is
// available in the upstream interface representation. At the the time of this
// writing, there is only need for an extension to Address data; should more
// general extensions be needed, a properties patch type which composes this
// type may be called for.
type addressPatch struct {
addr tcpip.AddressWithPrefix
validUntil time.Time
}
func makeInterfacesAddress(protocolAddr tcpip.ProtocolAddress, validUntil time.Time) interfaces.Address {
switch protocolAddr.Protocol {
case ipv4.ProtocolNumber:
case ipv6.ProtocolNumber:
default:
panic(fmt.Sprintf("makeInterfaceAddress(%+v, %s): unknown protocol", protocolAddr, validUntil))
}
var addr interfaces.Address
addr.SetAddr(net.Subnet{
Addr: fidlconv.ToNetIpAddress(protocolAddr.AddressWithPrefix.Address),
PrefixLen: uint8(protocolAddr.AddressWithPrefix.PrefixLen),
})
addr.SetValidUntil(validUntil.MonotonicNano())
return addr
}
func interfaceProperties(nicInfo tcpipstack.NICInfo, hasDefaultIPv4Route, hasDefaultIPv6Route bool, addressPatches []addressPatch) 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 {
panic(fmt.Sprintf("can't extract DeviceClass from non-loopback NIC %d(%s) with nil controller", ifs.nicid, nicInfo.Name))
}
ifs.mu.Lock()
p.SetOnline(ifs.IsUpLocked())
ifs.mu.Unlock()
var addrs []interfaces.Address
for _, a := range nicInfo.ProtocolAddresses {
addr := makeInterfacesAddress(a, time.Monotonic(int64(zx.TimensecInfinite)))
for _, p := range addressPatches {
if p.addr == a.AddressWithPrefix {
addr.SetValidUntil(p.validUntil.MonotonicNano())
break
}
}
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 = 128
func (wi *interfaceWatcherImpl) onEvent(e interfaces.Event) {
wi.mu.Lock()
if len(wi.mu.queue) >= maxInterfaceWatcherQueueLen {
_ = syslog.WarnTf(watcherProtocolName, "too many unconsumed events (client may not be calling Watch as frequently as possible): %d, max: %d", 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() {
p2Addr := p2.GetAddresses()[i]
if cmpSubnet(addr.GetAddr(), p2Addr.GetAddr()) != 0 {
return true
}
if addr.GetValidUntil() != p2Addr.GetValidUntil() {
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{}, errors.New("not allowed to call Watcher.Watch when a call is already pending")
}
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: %w", 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 (wc *interfaceWatcherCollection) onPropertiesChangeLocked(nicid tcpip.NICID, nicInfo tcpipstack.NICInfo, addressPatches []addressPatch) {
if properties, ok := wc.mu.lastObserved[nicid]; ok {
newProperties := interfaceProperties(nicInfo, properties.GetHasDefaultIpv4Route(), properties.GetHasDefaultIpv6Route(), addressPatches)
if diff := diffInterfaceProperties(properties, newProperties); !emptyInterfaceProperties(diff) {
wc.mu.lastObserved[nicid] = newProperties
for w := range wc.mu.watchers {
w.onEvent(interfaces.EventWithChanged(diff))
}
}
} else {
_ = syslog.WarnTf(watcherProtocolName, "onPropertiesChange called regarding unknown interface %d", nicid)
}
}
// onAddressAdd is called when an address is added.
//
// If performed, DAD must have completed successfully before calling this function, as the
// presence of an address indicates to clients that the address can be used.
func (wc *interfaceWatcherCollection) onAddressAdd(nicid tcpip.NICID, protocolAddr tcpip.ProtocolAddress, validUntil time.Time) {
wc.mu.Lock()
defer wc.mu.Unlock()
properties, ok := wc.mu.lastObserved[nicid]
if !ok {
_ = syslog.Warnf("onAddressAdd(%d, %+v, %s): NIC no longer exists", nicid, protocolAddr, validUntil)
return
}
if addrs, changed := func() ([]interfaces.Address, bool) {
if !properties.HasAddresses() {
return nil, true
} else {
addrs := properties.GetAddresses()
for _, a := range addrs {
subnet := a.GetAddr()
foundAddrWithPrefix := tcpip.AddressWithPrefix{
Address: fidlconv.ToTCPIPAddress(subnet.Addr),
PrefixLen: int(subnet.PrefixLen),
}
if foundAddrWithPrefix == protocolAddr.AddressWithPrefix {
_ = syslog.Warnf("onAddressAdd(%d, %+v, %s): address %+v already exists", nicid, protocolAddr, validUntil, foundAddrWithPrefix)
return nil, false
}
}
return addrs, true
}
}(); changed {
addrs = append(addrs, makeInterfacesAddress(protocolAddr, validUntil))
sort.Slice(addrs, func(i, j int) bool {
return cmpSubnet(addrs[i].GetAddr(), addrs[j].GetAddr()) <= 0
})
properties.SetAddresses(addrs)
wc.mu.lastObserved[nicid] = properties
var diff interfaces.Properties
diff.SetId(uint64(nicid))
diff.SetAddresses(addrs)
for w := range wc.mu.watchers {
w.onEvent(interfaces.EventWithChanged(diff))
}
}
}
func (wc *interfaceWatcherCollection) onDefaultRouteChangeLocked(routes []routes.ExtendedRoute) {
v4DefaultRoute := make(map[tcpip.NICID]struct{})
v6DefaultRoute := make(map[tcpip.NICID]struct{})
for _, er := range routes {
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 wc.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() {
wc.mu.lastObserved[nicid] = properties
for w := range wc.mu.watchers {
w.onEvent(interfaces.EventWithChanged(diff))
}
}
}
}
func (wc *interfaceWatcherCollection) onInterfaceAddLocked(nicid tcpip.NICID, nicInfo tcpipstack.NICInfo, routes []routes.ExtendedRoute) {
if properties, ok := wc.mu.lastObserved[nicid]; ok {
_ = syslog.WarnTf(watcherProtocolName, "interface added but already known: %+v", properties)
return
}
var hasDefaultIpv4Route, hasDefaultIpv6Route bool
for _, er := range routes {
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, nil)
wc.mu.lastObserved[nicid] = properties
for w := range wc.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(watcherProtocolName, "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.Serve(ctx, &interfaces.WatcherWithCtxStub{Impl: &impl}, watcher.Channel, component.ServeOptions{
Concurrent: true,
OnError: func(err error) {
_ = syslog.WarnTf(watcherProtocolName, "%s", err)
},
})
si.ns.interfaceWatchers.mu.Lock()
delete(si.ns.interfaceWatchers.mu.watchers, &impl)
si.ns.interfaceWatchers.mu.Unlock()
}()
return nil
}