blob: c254edf450e7cb3b70dcf1fcd2700c84a94cfef3 [file] [log] [blame]
// Copyright 2018 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 (
"fmt"
"sort"
"syslog"
"netstack/fidlconv"
"netstack/link"
"fidl/fuchsia/hardware/ethernet"
"fidl/fuchsia/net"
"fidl/fuchsia/net/stack"
"fidl/fuchsia/netstack"
"github.com/google/netstack/tcpip"
"github.com/google/netstack/tcpip/network/ipv4"
"github.com/google/netstack/tcpip/network/ipv6"
)
type stackImpl struct {
ns *Netstack
}
func getInterfaceInfo(ifs *ifState, addresses []tcpip.ProtocolAddress, subnets []tcpip.Subnet) *stack.InterfaceInfo {
ifs.mu.Lock()
defer ifs.mu.Unlock()
// TODO(tkilbourn): distinguish between enabled and link up
administrativeStatus := stack.AdministrativeStatusDisabled
physicalStatus := stack.PhysicalStatusDown
if ifs.mu.state == link.StateStarted {
administrativeStatus = stack.AdministrativeStatusEnabled
physicalStatus = stack.PhysicalStatusUp
}
addrs := make([]stack.InterfaceAddress, 0, len(addresses))
for _, a := range addresses {
if a.Protocol != ipv4.ProtocolNumber && a.Protocol != ipv6.ProtocolNumber {
continue
}
// We should always be guaranteed a matching subnet, since stack.NIC.Subnets constructs a subnet
// with an all-1s netmask for each endpoint on the NIC.
subnet := leastConstrainedMatchingSubnet(a.Address, subnets)
if subnet == (tcpip.Subnet{}) {
panic(fmt.Sprintf("NIC [%d]: address %+v has no matching subnet in %+v", ifs.nicid, a, subnets))
}
addrs = append(addrs, stack.InterfaceAddress{
IpAddress: fidlconv.ToNetIpAddress(a.Address),
PrefixLen: uint8(subnet.Prefix()),
})
}
var mac *ethernet.MacAddress
var path string
if eth := ifs.eth; eth != nil {
mac = &ethernet.MacAddress{}
copy(mac.Octets[:], ifs.endpoint.LinkAddress())
path = eth.Path()
}
return &stack.InterfaceInfo{
Id: uint64(ifs.nicid),
Properties: stack.InterfaceProperties{
Path: path,
Mac: mac,
Mtu: uint32(ifs.endpoint.MTU()),
AdministrativeStatus: administrativeStatus,
PhysicalStatus: physicalStatus,
Features: ifs.features,
Addresses: addrs,
},
}
}
func leastConstrainedMatchingSubnet(address tcpip.Address, subnets []tcpip.Subnet) tcpip.Subnet {
var subnet tcpip.Subnet
for _, s := range subnets {
if s.Contains(address) && !subnet.Contains(s.ID()) {
subnet = s
}
}
return subnet
}
func (ns *Netstack) getNetInterfaces() []stack.InterfaceInfo {
ns.mu.Lock()
out := make([]stack.InterfaceInfo, 0, len(ns.mu.ifStates))
for _, ifs := range ns.mu.ifStates {
addresses, subnets := ns.getAddressesLocked(ifs.nicid)
out = append(out, *getInterfaceInfo(ifs, addresses, subnets))
}
ns.mu.Unlock()
sort.Slice(out[:], func(i, j int) bool {
return out[i].Id < out[j].Id
})
return out
}
func (ns *Netstack) addInterface(topologicalPath string, device ethernet.DeviceInterface) (*stack.Error, uint64) {
var interfaceConfig netstack.InterfaceConfig
ifs, err := ns.addEth(topologicalPath, interfaceConfig, &device)
if err != nil {
return &stack.Error{Type: stack.ErrorTypeInternal}, 0
}
return nil, uint64(ifs.nicid)
}
func (ns *Netstack) delInterface(id uint64) *stack.Error {
ns.mu.Lock()
ifs, ok := ns.mu.ifStates[tcpip.NICID(id)]
ns.mu.Unlock()
if ok {
if err := ifs.eth.Close(); err != nil {
syslog.Errorf("ifs.eth.Close() failed (NIC: %d): %v", id, err)
return &stack.Error{Type: stack.ErrorTypeInternal}
}
return nil
}
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
func (ns *Netstack) getInterface(id uint64) (*stack.InterfaceInfo, *stack.Error) {
ns.mu.Lock()
ifs, ok := ns.mu.ifStates[tcpip.NICID(id)]
if !ok {
ns.mu.Unlock()
return nil, &stack.Error{Type: stack.ErrorTypeNotFound}
}
addresses, subnets := ns.getAddressesLocked(ifs.nicid)
ns.mu.Unlock()
return getInterfaceInfo(ifs, addresses, subnets), nil
}
func (ns *Netstack) setInterfaceState(id uint64, enabled bool) *stack.Error {
ns.mu.Lock()
ifs, ok := ns.mu.ifStates[tcpip.NICID(id)]
ns.mu.Unlock()
if !ok {
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
if enabled {
if err := ifs.eth.Up(); err != nil {
syslog.Errorf("ifs.eth.Up() failed (NIC %d): %v", id, err)
return &stack.Error{Type: stack.ErrorTypeInternal}
}
} else {
if err := ifs.eth.Down(); err != nil {
syslog.Errorf("ifs.eth.Down() failed (NIC %d): %v", id, err)
return &stack.Error{Type: stack.ErrorTypeInternal}
}
}
return nil
}
func (ns *Netstack) addInterfaceAddr(id uint64, ifAddr stack.InterfaceAddress) *stack.Error {
nicid := tcpip.NICID(id)
ns.mu.Lock()
_, ok := ns.mu.ifStates[nicid]
ns.mu.Unlock()
if !ok {
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
var protocol tcpip.NetworkProtocolNumber
switch typ := ifAddr.IpAddress.Which(); typ {
case net.IpAddressIpv4:
protocol = ipv4.ProtocolNumber
case net.IpAddressIpv6:
protocol = ipv6.ProtocolNumber
default:
panic(fmt.Sprintf("unknown IpAddress type %d", typ))
}
if err := ns.addInterfaceAddress(nicid, protocol, fidlconv.ToTCPIPAddress(ifAddr.IpAddress), ifAddr.PrefixLen); err != nil {
syslog.Errorf("(*Netstack).setInterfaceAddress(...) failed (NIC %d): %v", nicid, err)
return &stack.Error{Type: stack.ErrorTypeBadState}
}
return nil
}
func (ns *Netstack) getForwardingTable() []stack.ForwardingEntry {
ns.mu.Lock()
table := ns.mu.stack.GetRouteTable()
ns.mu.Unlock()
entries := make([]stack.ForwardingEntry, 0)
for _, route := range table {
entries = append(entries, fidlconv.TcpipRouteToForwardingEntry(route))
}
return entries
}
// equalSubnetAndRoute returns true if and only if the route matches
// the subnet. Only the the ip and subnet are compared and must be
// exact.
func equalSubnetAndRoute(subnet net.Subnet, tcpipRoute tcpip.Route) bool {
return fidlconv.ToTCPIPAddress(subnet.Addr) == tcpipRoute.Destination &&
subnet.PrefixLen == fidlconv.GetPrefixLen(tcpipRoute.Mask)
}
// validateSubnet returns true if the prefix length is valid and no
// address bits are set beyond the prefix length.
func validateSubnet(subnet net.Subnet) bool {
var ipBytes []uint8
switch typ := subnet.Addr.Which(); typ {
case net.IpAddressIpv4:
ipBytes = subnet.Addr.Ipv4.Addr[:]
case net.IpAddressIpv6:
ipBytes = subnet.Addr.Ipv6.Addr[:]
default:
panic(fmt.Sprintf("unknown IpAddress type %d", typ))
}
if int(subnet.PrefixLen) > len(ipBytes)*8 {
return false
}
prefixBytes := subnet.PrefixLen / 8
ipBytes = ipBytes[prefixBytes:]
if prefixBits := subnet.PrefixLen - (prefixBytes * 8); prefixBits > 0 {
// prefixBits is only greater than zero when ipBytes is non-empty.
mask := uint8((1 << (8 - prefixBits)) - 1)
ipBytes[0] &= mask
}
for _, byte := range ipBytes {
if byte != 0 {
return false
}
}
return true
}
func (ns *Netstack) addForwardingEntry(entry stack.ForwardingEntry) *stack.Error {
if !validateSubnet(entry.Subnet) {
return &stack.Error{Type: stack.ErrorTypeInvalidArgs}
}
ns.mu.Lock()
defer ns.mu.Unlock()
table := ns.mu.stack.GetRouteTable()
for _, route := range table {
if equalSubnetAndRoute(entry.Subnet, route) {
return &stack.Error{Type: stack.ErrorTypeAlreadyExists}
}
}
ns.mu.stack.SetRouteTable(append(table, fidlconv.ForwardingEntryToTcpipRoute(entry)))
return nil
}
func (ns *Netstack) delForwardingEntry(subnet net.Subnet) *stack.Error {
if !validateSubnet(subnet) {
return &stack.Error{Type: stack.ErrorTypeInvalidArgs}
}
ns.mu.Lock()
defer ns.mu.Unlock()
table := ns.mu.stack.GetRouteTable()
for i, route := range table {
if equalSubnetAndRoute(subnet, route) {
table[i] = table[len(table)-1]
table = table[:len(table)-1]
ns.mu.stack.SetRouteTable(table)
return nil
}
}
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
func (ns *Netstack) enablePacketFilter(id uint64) *stack.Error {
ns.mu.Lock()
ifs, ok := ns.mu.ifStates[tcpip.NICID(id)]
ns.mu.Unlock()
if !ok || ifs.filterEndpoint == nil {
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
ifs.filterEndpoint.Enable()
return nil
}
func (ns *Netstack) disablePacketFilter(id uint64) *stack.Error {
ns.mu.Lock()
ifs, ok := ns.mu.ifStates[tcpip.NICID(id)]
ns.mu.Unlock()
if !ok || ifs.filterEndpoint == nil {
return &stack.Error{Type: stack.ErrorTypeNotFound}
}
ifs.filterEndpoint.Enable()
return nil
}
func (ni *stackImpl) AddEthernetInterface(topologicalPath string, device ethernet.DeviceInterface) (*stack.Error, uint64, error) {
err, id := ni.ns.addInterface(topologicalPath, device)
return err, id, nil
}
func (ni *stackImpl) DelEthernetInterface(id uint64) (*stack.Error, error) {
return ni.ns.delInterface(id), nil
}
func (ni *stackImpl) ListInterfaces() ([]stack.InterfaceInfo, error) {
return ni.ns.getNetInterfaces(), nil
}
func (ni *stackImpl) GetInterfaceInfo(id uint64) (*stack.InterfaceInfo, *stack.Error, error) {
info, err := ni.ns.getInterface(id)
return info, err, nil
}
func (ni *stackImpl) EnableInterface(id uint64) (*stack.Error, error) {
return ni.ns.setInterfaceState(id, true), nil
}
func (ni *stackImpl) DisableInterface(id uint64) (*stack.Error, error) {
return ni.ns.setInterfaceState(id, false), nil
}
func (ni *stackImpl) AddInterfaceAddress(id uint64, addr stack.InterfaceAddress) (*stack.Error, error) {
return ni.ns.addInterfaceAddr(id, addr), nil
}
func (ni *stackImpl) DelInterfaceAddress(id uint64, addr stack.InterfaceAddress) (*stack.Error, error) {
ni.ns.mu.Lock()
_, ok := ni.ns.mu.ifStates[tcpip.NICID(id)]
ni.ns.mu.Unlock()
if !ok {
return &stack.Error{Type: stack.ErrorTypeNotFound}, nil
}
var protocol tcpip.NetworkProtocolNumber
switch tag := addr.IpAddress.Which(); tag {
case net.IpAddressIpv4:
protocol = ipv4.ProtocolNumber
case net.IpAddressIpv6:
protocol = ipv6.ProtocolNumber
default:
panic(fmt.Sprintf("unknown IpAddress type %d", tag))
}
if err := ni.ns.removeInterfaceAddress(tcpip.NICID(id), protocol, fidlconv.ToTCPIPAddress(addr.IpAddress), uint8(addr.PrefixLen)); err != nil {
syslog.Errorf("failed to remove interface address: %s", err)
return &stack.Error{Type: stack.ErrorTypeInternal}, nil
}
return nil, nil
}
func (ni *stackImpl) GetForwardingTable() ([]stack.ForwardingEntry, error) {
return ni.ns.getForwardingTable(), nil
}
func (ni *stackImpl) AddForwardingEntry(entry stack.ForwardingEntry) (*stack.Error, error) {
return ni.ns.addForwardingEntry(entry), nil
}
func (ni *stackImpl) DelForwardingEntry(subnet net.Subnet) (*stack.Error, error) {
return ni.ns.delForwardingEntry(subnet), nil
}
func (ni *stackImpl) EnablePacketFilter(id uint64) (*stack.Error, error) {
return ni.ns.enablePacketFilter(id), nil
}
func (ni *stackImpl) DisablePacketFilter(id uint64) (*stack.Error, error) {
return ni.ns.disablePacketFilter(id), nil
}