| // 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("failed to call stack.GetMainNICAddress(_): %s", 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.Errorf("(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 |
| } |