blob: 963cf1bb330e59edaa872e818050e82ce244f7fe [file] [log] [blame]
// 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.
// +build !build_with_native_toolchain
package netstack
import (
"context"
"errors"
"net"
"sort"
"syscall/zx"
"syscall/zx/fidl"
"syscall/zx/zxwait"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/fidlconv"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/link/eth"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routes"
"go.fuchsia.dev/fuchsia/src/lib/component"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
"fidl/fuchsia/hardware/ethernet"
fidlnet "fidl/fuchsia/net"
"fidl/fuchsia/net/dhcp"
"fidl/fuchsia/netstack"
"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"
)
type netstackImpl struct {
ns *Netstack
}
// interfaces2ListToInterfacesList converts a NetInterface2 list into a
// NetInterface one.
func interfaces2ListToInterfacesList(ifs2 []netstack.NetInterface2) []netstack.NetInterface {
ifs := make([]netstack.NetInterface, 0, len(ifs2))
for _, e2 := range ifs2 {
ifs = append(ifs, netstack.NetInterface{
Id: e2.Id,
Flags: e2.Flags,
Features: e2.Features,
Name: e2.Name,
Addr: e2.Addr,
Netmask: e2.Netmask,
Broadaddr: e2.Broadaddr,
Hwaddr: e2.Hwaddr,
Ipv6addrs: e2.Ipv6addrs,
})
}
return ifs
}
func (ns *Netstack) getNetInterfaces2() []netstack.NetInterface2 {
nicInfos := ns.stack.NICInfo()
interfaces := make([]netstack.NetInterface2, 0, len(nicInfos))
for _, nicInfo := range nicInfos {
ifs := nicInfo.Context.(*ifState)
var ipv6addrs []fidlnet.Subnet
for _, address := range nicInfo.ProtocolAddresses {
if address.Protocol == ipv6.ProtocolNumber {
ipv6addrs = append(ipv6addrs, fidlnet.Subnet{
Addr: fidlconv.ToNetIpAddress(address.AddressWithPrefix.Address),
PrefixLen: uint8(address.AddressWithPrefix.PrefixLen),
})
}
}
var flags netstack.Flags
ifs.mu.Lock()
if ifs.IsUpLocked() {
flags |= netstack.FlagsUp
}
if ifs.mu.dhcp.enabled {
flags |= netstack.FlagsDhcp
}
metric := uint32(ifs.mu.metric)
ifs.mu.Unlock()
netInterface := netstack.NetInterface2{
Id: uint32(ifs.nicid),
Flags: flags,
Metric: metric,
Name: nicInfo.Name,
Hwaddr: []uint8(ifs.endpoint.LinkAddress()[:]),
Ipv6addrs: ipv6addrs,
}
if ifs.endpoint.Capabilities()&tcpipstack.CapabilityLoopback != 0 {
netInterface.Features |= ethernet.FeaturesLoopback
}
if client, ok := ifs.controller.(*eth.Client); ok {
netInterface.Features |= client.Info.Features
}
addrWithPrefix, err := ifs.ns.stack.GetMainNICAddress(ifs.nicid, ipv4.ProtocolNumber)
if err != nil {
_ = syslog.Warnf("stack.GetMainNICAddress(%d, ipv4.ProtocolNumber): %s", ifs.nicid, err)
}
if addrWithPrefix == (tcpip.AddressWithPrefix{}) {
addrWithPrefix = tcpip.AddressWithPrefix{Address: header.IPv4Any, PrefixLen: 0}
}
mask := net.CIDRMask(addrWithPrefix.PrefixLen, len(addrWithPrefix.Address)*8)
broadaddr := []byte(addrWithPrefix.Address)
for i := range broadaddr {
broadaddr[i] |= ^mask[i]
}
netInterface.Addr = fidlconv.ToNetIpAddress(addrWithPrefix.Address)
netInterface.Netmask = fidlconv.ToNetIpAddress(tcpip.Address(mask))
netInterface.Broadaddr = fidlconv.ToNetIpAddress(tcpip.Address(broadaddr))
interfaces = append(interfaces, netInterface)
}
return interfaces
}
func (ns *Netstack) getInterfaces2() []netstack.NetInterface2 {
out := ns.getNetInterfaces2()
sort.Slice(out, func(i, j int) bool {
return out[i].Id < out[j].Id
})
return out
}
// GetInterfaces2 returns a list of interfaces.
// TODO(fxbug.dev/21079): Move this to GetInterfaces once Chromium stops using
// netstack.fidl.
func (ni *netstackImpl) GetInterfaces2(fidl.Context) ([]netstack.NetInterface2, error) {
return ni.ns.getInterfaces2(), nil
}
// GetInterfaces is a deprecated version that returns a list of interfaces in a
// format that Chromium supports. The new version is GetInterfaces2 which
// eventually will be renamed once Chromium is not using netstack.fidl anymore
// and this deprecated version can be removed.
func (ni *netstackImpl) GetInterfaces(fidl.Context) ([]netstack.NetInterface, error) {
// Get the new interface list and convert to the old one.
return interfaces2ListToInterfacesList(ni.ns.getInterfaces2()), nil
}
// TODO(fxbug.dev/21079): Move this to GetRouteTable once Chromium stops using
// netstack.fidl.
func (ni *netstackImpl) GetRouteTable2(fidl.Context) ([]netstack.RouteTableEntry2, error) {
return nsToRouteTable2(ni.ns.GetExtendedRouteTable()), nil
}
// GetRouteTable is a deprecated version that returns the route table in a
// format that Chromium supports. The new version is GetRouteTable2 which will
// eventually be renamed once Chromium is not using netstack.fidl anymore and
// this deprecated version can be removed.
func (ni *netstackImpl) GetRouteTable(fidl.Context) ([]netstack.RouteTableEntry, error) {
rt2 := nsToRouteTable2(ni.ns.GetExtendedRouteTable())
rt := make([]netstack.RouteTableEntry, 0, len(rt2))
for _, r2 := range rt2 {
var gateway fidlnet.IpAddress
if r2.Gateway != nil {
gateway = *r2.Gateway
} else {
gateway = fidlconv.ToNetIpAddress(header.IPv4Any)
}
rt = append(rt, netstack.RouteTableEntry{
Destination: r2.Destination,
Netmask: r2.Netmask,
Gateway: gateway,
Nicid: r2.Nicid,
})
}
return rt, nil
}
func nsToRouteTable2(table []routes.ExtendedRoute) []netstack.RouteTableEntry2 {
out := make([]netstack.RouteTableEntry2, 0, len(table))
for _, e := range table {
var gatewayPtr *fidlnet.IpAddress
if len(e.Route.Gateway) != 0 {
gateway := fidlconv.ToNetIpAddress(e.Route.Gateway)
gatewayPtr = &gateway
}
out = append(out, netstack.RouteTableEntry2{
Destination: fidlconv.ToNetIpAddress(e.Route.Destination.ID()),
Netmask: fidlconv.ToNetIpAddress(tcpip.Address(e.Route.Destination.Mask())),
Gateway: gatewayPtr,
Nicid: uint32(e.Route.NIC),
Metric: uint32(e.Metric),
})
}
return out
}
func routeToNs(r netstack.RouteTableEntry2) tcpip.Route {
prefixLen, _ := net.IPMask(fidlconv.ToTCPIPAddress(r.Netmask)).Size()
route := tcpip.Route{
Destination: fidlconv.ToTCPIPSubnet(fidlnet.Subnet{
Addr: r.Destination,
PrefixLen: uint8(prefixLen),
}),
NIC: tcpip.NICID(r.Nicid),
}
if g := r.Gateway; g != nil {
route.Gateway = fidlconv.ToTCPIPAddress(*g)
}
return route
}
type routeTableTransactionImpl struct {
ni *netstackImpl
}
func (i *routeTableTransactionImpl) AddRoute(_ fidl.Context, r netstack.RouteTableEntry2) (int32, error) {
err := i.ni.ns.AddRoute(routeToNs(r), routes.Metric(r.Metric), false /* not dynamic */)
if err != nil {
return int32(zx.ErrInvalidArgs), err
}
return int32(zx.ErrOk), nil
}
func (i *routeTableTransactionImpl) DelRoute(_ fidl.Context, r netstack.RouteTableEntry2) (int32, error) {
err := i.ni.ns.DelRoute(routeToNs(r))
if err != nil {
return int32(zx.ErrInvalidArgs), err
}
return int32(zx.ErrOk), nil
}
func (ni *netstackImpl) StartRouteTableTransaction(_ fidl.Context, req netstack.RouteTableTransactionWithCtxInterfaceRequest) (int32, error) {
{
ni.ns.mu.Lock()
defer ni.ns.mu.Unlock()
if ni.ns.mu.transactionRequest != nil {
oldChannel := ni.ns.mu.transactionRequest.Channel
observed, _ := zxwait.Wait(zx.Handle(oldChannel), 0, 0)
// If the channel is neither readable nor writable, there is no
// data left to be processed (not readable) and we can't return
// any more results (not writable). It's not enough to only
// look at peerclosed because the peer can close the channel
// while it still has data in its buffers.
if observed&(zx.SignalChannelReadable|zx.SignalChannelWritable) == 0 {
ni.ns.mu.transactionRequest = nil
}
}
if ni.ns.mu.transactionRequest != nil {
return int32(zx.ErrShouldWait), nil
}
ni.ns.mu.transactionRequest = &req
}
stub := netstack.RouteTableTransactionWithCtxStub{Impl: &routeTableTransactionImpl{ni: ni}}
go component.ServeExclusive(context.Background(), &stub, req.Channel, func(err error) {
// NB: this protocol is not discoverable, so the bindings do not include its name.
_ = syslog.WarnTf("fuchsia.netstack.RouteTableTransaction", "%s", err)
})
return int32(zx.ErrOk), nil
}
// Add address to the given network interface.
func (ni *netstackImpl) SetInterfaceAddress(_ fidl.Context, nicid uint32, address fidlnet.IpAddress, prefixLen uint8) (netstack.NetErr, error) {
protocolAddr := toProtocolAddr(fidlnet.Subnet{
Addr: address,
PrefixLen: prefixLen,
})
if protocolAddr.AddressWithPrefix.PrefixLen > 8*len(protocolAddr.AddressWithPrefix.Address) {
return netstack.NetErr{Status: netstack.StatusParseError, Message: "prefix length exceeds address length"}, nil
}
found, err := ni.ns.addInterfaceAddress(tcpip.NICID(nicid), protocolAddr)
if err != nil {
return netstack.NetErr{Status: netstack.StatusUnknownError, Message: err.Error()}, nil
}
if !found {
return netstack.NetErr{Status: netstack.StatusUnknownInterface}, nil
}
return netstack.NetErr{Status: netstack.StatusOk}, nil
}
func (ni *netstackImpl) RemoveInterfaceAddress(_ fidl.Context, nicid uint32, address fidlnet.IpAddress, prefixLen uint8) (netstack.NetErr, error) {
protocolAddr := toProtocolAddr(fidlnet.Subnet{
Addr: address,
PrefixLen: prefixLen,
})
if protocolAddr.AddressWithPrefix.PrefixLen > 8*len(protocolAddr.AddressWithPrefix.Address) {
return netstack.NetErr{Status: netstack.StatusParseError, Message: "prefix length exceeds address length"}, nil
}
found, err := ni.ns.removeInterfaceAddress(tcpip.NICID(nicid), protocolAddr)
if err != nil {
return netstack.NetErr{Status: netstack.StatusUnknownError, Message: err.Error()}, nil
}
if !found {
return netstack.NetErr{Status: netstack.StatusUnknownInterface}, nil
}
return netstack.NetErr{Status: netstack.StatusOk}, nil
}
// SetInterfaceMetric changes the metric for an interface and updates all
// routes tracking that interface metric. This takes the lock.
func (ni *netstackImpl) SetInterfaceMetric(_ fidl.Context, nicid uint32, metric uint32) (result netstack.NetErr, err error) {
syslog.Infof("update interface metric for NIC %d to metric=%d", nicid, metric)
nic := tcpip.NICID(nicid)
m := routes.Metric(metric)
nicInfo, ok := ni.ns.stack.NICInfo()[nic]
if !ok {
return netstack.NetErr{Status: netstack.StatusUnknownInterface}, nil
}
ifState := nicInfo.Context.(*ifState)
ifState.updateMetric(m)
ni.ns.routeTable.UpdateMetricByInterface(nic, m)
ni.ns.routeTable.UpdateStack(ni.ns.stack)
return netstack.NetErr{Status: netstack.StatusOk}, nil
}
func (ni *netstackImpl) BridgeInterfaces(_ fidl.Context, nicids []uint32) (netstack.NetErr, uint32, error) {
nics := make([]tcpip.NICID, len(nicids))
for i, n := range nicids {
nics[i] = tcpip.NICID(n)
}
ifs, err := ni.ns.Bridge(nics)
if err != nil {
return netstack.NetErr{Status: netstack.StatusUnknownError, Message: err.Error()}, 0, nil
}
return netstack.NetErr{Status: netstack.StatusOk}, uint32(ifs.nicid), nil
}
func (ni *netstackImpl) SetInterfaceStatus(_ fidl.Context, nicid uint32, enabled bool) error {
if nicInfo, ok := ni.ns.stack.NICInfo()[tcpip.NICID(nicid)]; ok {
if err := nicInfo.Context.(*ifState).setState(enabled); err != nil {
_ = syslog.Errorf("(NIC %d).setState(enabled=%t): %s", nicid, enabled, err)
}
} else {
_ = syslog.Warnf("(NIC %d).setState(enabled=%t): not found", nicid, enabled)
}
return nil
}
func (ni *netstackImpl) GetDhcpClient(ctx fidl.Context, id uint32, request dhcp.ClientWithCtxInterfaceRequest) (netstack.NetstackGetDhcpClientResult, error) {
var result netstack.NetstackGetDhcpClientResult
nicid := tcpip.NICID(id)
if _, ok := ni.ns.stack.NICInfo()[nicid]; !ok {
result.SetErr(int32(zx.ErrNotFound))
_ = request.Close()
return result, nil
}
stub := dhcp.ClientWithCtxStub{Impl: &clientImpl{ns: ni.ns, nicid: nicid}}
go component.ServeExclusive(context.Background(), &stub, request.Channel, func(err error) {
// NB: this protocol is not discoverable, so the bindings do not include its name.
_ = syslog.WarnTf("fuchsia.net.dhcp.Client", "%s", err)
})
result.SetResponse(netstack.NetstackGetDhcpClientResponse{})
return result, nil
}
func (ns *netstackImpl) AddEthernetDevice(_ fidl.Context, topopath string, interfaceConfig netstack.InterfaceConfig, device ethernet.DeviceWithCtxInterface) (netstack.NetstackAddEthernetDeviceResult, error) {
var result netstack.NetstackAddEthernetDeviceResult
if ifs, err := ns.ns.addEth(topopath, interfaceConfig, &device); err != nil {
var tcpipErr *TcpIpError
if errors.As(err, &tcpipErr) {
result.SetErr(int32(tcpipErr.ToZxStatus()))
} else {
result.SetErr(int32(zx.ErrInternal))
}
} else {
result.SetResponse(netstack.NetstackAddEthernetDeviceResponse{Nicid: uint32(ifs.nicid)})
}
return result, nil
}