blob: d9d6c8cc7ab64261d4e26c2f792c43569a2b8675 [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.
//go:build !build_with_native_toolchain
package fidlconv
import (
"fmt"
stdnet "net"
"net/netip"
"syscall/zx"
"time"
"fidl/fuchsia/net"
"fidl/fuchsia/net/interfaces"
interfacesadmin "fidl/fuchsia/net/interfaces/admin"
"fidl/fuchsia/net/multicast/admin"
fnetRoutes "fidl/fuchsia/net/routes"
"fidl/fuchsia/net/stack"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routetypes"
"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 ToTCPIPNetProto(v net.IpVersion) (tcpip.NetworkProtocolNumber, bool) {
switch v {
case net.IpVersionV4:
return header.IPv4ProtocolNumber, true
case net.IpVersionV6:
return header.IPv6ProtocolNumber, true
default:
return 0, false
}
}
func ToTCPIPAddressAndProtocolNumber(addr net.IpAddress) (tcpip.Address, tcpip.NetworkProtocolNumber) {
switch tag := addr.Which(); tag {
case net.IpAddressIpv4:
return tcpip.AddrFrom4(addr.Ipv4.Addr), ipv4.ProtocolNumber
case net.IpAddressIpv6:
return tcpip.AddrFrom16(addr.Ipv6.Addr), ipv6.ProtocolNumber
default:
panic(fmt.Sprintf("invalid fuchsia.net/IpAddress tag %d", tag))
}
}
func ToTCPIPAddress(addr net.IpAddress) tcpip.Address {
a, _ := ToTCPIPAddressAndProtocolNumber(addr)
return a
}
func ToStdAddr(addr net.IpAddress) netip.Addr {
switch tag := addr.Which(); tag {
case net.IpAddressIpv4:
return netip.AddrFrom4(addr.Ipv4.Addr)
case net.IpAddressIpv6:
return netip.AddrFrom16(addr.Ipv6.Addr)
default:
panic(fmt.Sprintf("invalid fuchsia.net/IpAddress tag %d", tag))
}
}
func ToNetIpAddress(addr tcpip.Address) net.IpAddress {
switch l := addr.Len(); l {
case header.IPv4AddressSize:
var v4 net.Ipv4Address
copy(v4.Addr[:], addr.AsSlice())
return net.IpAddressWithIpv4(v4)
case header.IPv6AddressSize:
var v6 net.Ipv6Address
copy(v6.Addr[:], addr.AsSlice())
return net.IpAddressWithIpv6(v6)
default:
panic(fmt.Sprintf("invalid IP address length = %d: %x", l, addr))
}
}
func ToNetSubnet(addressWithPrefix tcpip.AddressWithPrefix) net.Subnet {
return net.Subnet{
Addr: ToNetIpAddress(addressWithPrefix.Address),
PrefixLen: uint8(addressWithPrefix.PrefixLen),
}
}
func ToNetMacAddress(addr tcpip.LinkAddress) net.MacAddress {
if len(addr) != header.EthernetAddressSize {
panic(fmt.Sprintf("invalid link address length = %d: %x", len(addr), addr))
}
var mac net.MacAddress
copy(mac.Octets[:], addr)
return mac
}
func ToTCPIPLinkAddress(mac net.MacAddress) tcpip.LinkAddress {
return tcpip.LinkAddress(mac.Octets[:])
}
func ToNetSocketAddress(addr tcpip.FullAddress) net.SocketAddress {
var out net.SocketAddress
switch l := addr.Addr.Len(); l {
case header.IPv4AddressSize:
var v4 net.Ipv4Address
copy(v4.Addr[:], addr.Addr.AsSlice())
out.SetIpv4(net.Ipv4SocketAddress{
Address: v4,
Port: addr.Port,
})
case header.IPv6AddressSize:
var v6 net.Ipv6Address
copy(v6.Addr[:], addr.Addr.AsSlice())
// Zone information should only be included for non-global addresses as the same
// address may be used across different zones. Note, there is only a single globally
// scoped zone where global addresses may only be used once so zone information is not
// needed for global addresses. See RFC 4007 section 6 for more details.
var zoneIdx uint64
if header.IsV6LinkLocalUnicastAddress(addr.Addr) || header.IsV6LinkLocalMulticastAddress(addr.Addr) {
zoneIdx = uint64(addr.NIC)
}
out.SetIpv6(net.Ipv6SocketAddress{
Address: v6,
Port: addr.Port,
ZoneIndex: zoneIdx,
})
default:
panic(fmt.Sprintf("invalid IP address length = %d: %x", l, addr.Addr))
}
return out
}
// isLinkLocal determines if the given IPv6 address is link-local. This is the
// case when it has the fe80::/10 prefix. This check is used to determine when
// the NICID is relevant for a given IPv6 address.
func isLinkLocal(addr net.Ipv6Address) bool {
return addr.Addr[0] == 0xfe && addr.Addr[1]&0xc0 == 0x80
}
// ToNetSocketAddress converts a tcpip.FullAddress into a fnet.SocketAddress
// taking the protocol into consideration. If addr is unspecified, the
// unspecified address for the provided protocol is returned.
//
// Panics if protocol is neither IPv4 nor IPv6.
func ToNetSocketAddressWithProto(protocol tcpip.NetworkProtocolNumber, addr tcpip.FullAddress) net.SocketAddress {
switch protocol {
case ipv4.ProtocolNumber:
out := net.Ipv4SocketAddress{
Port: addr.Port,
}
copy(out.Address.Addr[:], addr.Addr.AsSlice())
return net.SocketAddressWithIpv4(out)
case ipv6.ProtocolNumber:
out := net.Ipv6SocketAddress{
Port: addr.Port,
}
if addr.Addr.Len() == header.IPv4AddressSize {
// Copy address in v4-mapped format.
copy(out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize:], addr.Addr.AsSlice())
out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize-1] = 0xff
out.Address.Addr[header.IPv6AddressSize-header.IPv4AddressSize-2] = 0xff
} else {
copy(out.Address.Addr[:], addr.Addr.AsSlice())
if isLinkLocal(out.Address) {
out.ZoneIndex = uint64(addr.NIC)
}
}
return net.SocketAddressWithIpv6(out)
default:
panic(fmt.Sprintf("invalid protocol for conversion: %d", protocol))
}
}
func ToTCPIPAddressWithPrefix(sn net.Subnet) tcpip.AddressWithPrefix {
return tcpip.AddressWithPrefix{
Address: ToTCPIPAddress(sn.Addr),
PrefixLen: int(sn.PrefixLen),
}
}
func ToTCPIPSubnet(sn net.Subnet) tcpip.Subnet {
return ToTCPIPAddressWithPrefix(sn).Subnet()
}
// ToTCPIPSubnetChecked converts the fuchsia.net.Subnet into a tcpip.Subnet,
// returning an error if the subnet prefix length is invalid or if the subnet
// address has host bits set.
func ToTCPIPSubnetChecked(sn net.Subnet) (tcpip.Subnet, error) {
ones := sn.PrefixLen
addr := ToTCPIPAddress(sn.Addr)
return tcpip.NewSubnet(addr, tcpip.MaskFromBytes(stdnet.CIDRMask(int(ones), addr.BitLen())))
}
func ToTCPIPProtocolAddress(sn net.Subnet) tcpip.ProtocolAddress {
protocolAddr := tcpip.ProtocolAddress{
AddressWithPrefix: ToTCPIPAddressWithPrefix(sn),
}
switch typ := sn.Addr.Which(); typ {
case net.IpAddressIpv4:
protocolAddr.Protocol = ipv4.ProtocolNumber
case net.IpAddressIpv6:
protocolAddr.Protocol = ipv6.ProtocolNumber
default:
panic(fmt.Sprintf("unknown IpAddress type %d", typ))
}
return protocolAddr
}
func TCPIPRouteToForwardingEntry(route tcpip.Route) stack.ForwardingEntry {
forwardingEntry := stack.ForwardingEntry{
Subnet: net.Subnet{
Addr: ToNetIpAddress(route.Destination.ID()),
PrefixLen: uint8(route.Destination.Prefix()),
},
DeviceId: uint64(route.NIC),
}
if route.Gateway.Len() != 0 {
nextHop := ToNetIpAddress(route.Gateway)
forwardingEntry.NextHop = &nextHop
}
return forwardingEntry
}
func ForwardingEntryToTCPIPRoute(forwardingEntry stack.ForwardingEntry) tcpip.Route {
route := tcpip.Route{
Destination: ToTCPIPSubnet(forwardingEntry.Subnet),
NIC: tcpip.NICID(forwardingEntry.DeviceId),
}
if nextHop := forwardingEntry.NextHop; nextHop != nil {
route.Gateway = ToTCPIPAddress(*nextHop)
}
return route
}
// ToStackMulticastRoute converts the provided route to a stack multicast
// route.
//
// Returns true if the provided route contains all required fields and the
// conversion was successful. Otherwise, returns false.
func ToStackMulticastRoute(route admin.Route) (tcpipstack.MulticastRoute, bool) {
if !route.HasExpectedInputInterface() || !route.HasAction() {
return tcpipstack.MulticastRoute{}, false
}
outgoingInterfaces := make([]tcpipstack.MulticastRouteOutgoingInterface, 0, len(route.Action.OutgoingInterfaces))
for _, outgoingInterface := range route.Action.OutgoingInterfaces {
outgoingInterfaces = append(outgoingInterfaces, tcpipstack.MulticastRouteOutgoingInterface{
ID: tcpip.NICID(outgoingInterface.Id),
MinTTL: outgoingInterface.MinTtl,
})
}
return tcpipstack.MulticastRoute{
ExpectedInputInterface: tcpip.NICID(route.ExpectedInputInterface),
OutgoingInterfaces: outgoingInterfaces,
}, true
}
// TODO(https://fxbug.dev/42181878): Replace usages with `Clock.Elapsed` when available.
func ToZxTime(t tcpip.MonotonicTime) zx.Time {
return zx.Time(t.Sub(tcpip.MonotonicTime{}))
}
func ToTCPIPMonotonicTime(zxtime zx.Time) tcpip.MonotonicTime {
return tcpip.MonotonicTime{}.Add(time.Duration(zxtime))
}
func BytesToAddressDroppingUnspecified(b []uint8) tcpip.Address {
for _, e := range b {
if e != 0 {
return tcpip.AddrFromSlice(b)
}
}
return tcpip.Address{}
}
func ToTCPIPFullAddress(addr net.SocketAddress) tcpip.FullAddress {
switch w := addr.Which(); w {
case net.SocketAddressIpv4:
return tcpip.FullAddress{
NIC: 0,
Addr: BytesToAddressDroppingUnspecified(addr.Ipv4.Address.Addr[:]),
Port: addr.Ipv4.Port,
}
case net.SocketAddressIpv6:
return tcpip.FullAddress{
NIC: tcpip.NICID(addr.Ipv6.ZoneIndex),
Addr: BytesToAddressDroppingUnspecified(addr.Ipv6.Address.Addr[:]),
Port: addr.Ipv6.Port,
}
default:
panic(fmt.Sprintf("invalid fuchsia.net/SocketAddress variant: %d", w))
}
}
func ToTcpIpAddressDroppingUnspecifiedv4(fidl net.Ipv4Address) tcpip.Address {
return BytesToAddressDroppingUnspecified(fidl.Addr[:])
}
func ToTcpIpAddressDroppingUnspecifiedv6(fidl net.Ipv6Address) tcpip.Address {
return BytesToAddressDroppingUnspecified(fidl.Addr[:])
}
func ToAddressAssignmentState(state tcpipstack.AddressAssignmentState) interfaces.AddressAssignmentState {
switch state {
case tcpipstack.AddressDisabled:
return interfaces.AddressAssignmentStateUnavailable
case tcpipstack.AddressAssigned:
return interfaces.AddressAssignmentStateAssigned
case tcpipstack.AddressTentative:
return interfaces.AddressAssignmentStateTentative
default:
panic(fmt.Errorf("unknown address assignment state: %d", state))
}
}
func ToAddressRemovalReason(reason tcpipstack.AddressRemovalReason) interfacesadmin.AddressRemovalReason {
switch reason {
case tcpipstack.AddressRemovalDADFailed:
return interfacesadmin.AddressRemovalReasonDadFailed
case tcpipstack.AddressRemovalInterfaceRemoved:
return interfacesadmin.AddressRemovalReasonInterfaceRemoved
case tcpipstack.AddressRemovalManualAction:
return interfacesadmin.AddressRemovalReasonUserRemoved
// TODO(https://fxbug.dev/42065170): When invalidation for all addresses are
// handled in-stack, change this to return an appropriate FIDL variant instead
// of panicking.
// The invalidated removal reason is only returned for addresses auto-generated
// within the stack, and cannot be returned for addresses added via
// fuchsia.net.interfaces.admin/Control.AddAddress.
case tcpipstack.AddressRemovalInvalidated:
panic("unexpected address removal due to invalidation")
default:
panic(fmt.Errorf("unknown address removal reason: %d", reason))
}
}
// A union type, abstracting over `InstalledRouteV4` and `InstalledRouteV6`.
type InstalledRoute struct {
Version routetypes.IpProtoTag
V4 fnetRoutes.InstalledRouteV4
V6 fnetRoutes.InstalledRouteV6
}
// Converts the given `ExtendedRoute` into an `InstalledRoute`.
// Panics if the given route is neither IPv4 nor IPv6, or if the route's
// destination & gateway are different IP versions.
func ToInstalledRoute(route routetypes.ExtendedRoute) InstalledRoute {
var specifiedMetric fnetRoutes.SpecifiedMetric
if route.MetricTracksInterface {
specifiedMetric.SetInheritedFromInterface(fnetRoutes.Empty{})
} else {
specifiedMetric.SetExplicitMetric(uint32(route.Metric))
}
var specifiedProperties fnetRoutes.SpecifiedRouteProperties
specifiedProperties.SetMetric(specifiedMetric)
var effectiveProperties fnetRoutes.EffectiveRouteProperties
effectiveProperties.SetMetric(uint32(route.Metric))
dst := ToNetIpAddress(route.Route.Destination.ID())
switch dst.Which() {
case net.IpAddressIpv4:
destination := net.Ipv4AddressWithPrefix{
Addr: dst.Ipv4,
PrefixLen: uint8(route.Route.Destination.Prefix()),
}
target := fnetRoutes.RouteTargetV4{
OutboundInterface: uint64(route.Route.NIC),
}
if route.Route.Gateway.Len() != 0 {
gateway := ToNetIpAddress(route.Route.Gateway)
if gateway.Which() != net.IpAddressIpv4 {
panic(fmt.Sprintf(
"Route with IPv4 Destination and non-IPv4 Gateway: %s",
route.Route,
))
}
target.NextHop = &gateway.Ipv4
}
var properties fnetRoutes.RoutePropertiesV4
properties.SetSpecifiedProperties(specifiedProperties)
innerRoute := fnetRoutes.RouteV4{
Destination: destination,
Action: fnetRoutes.RouteActionV4WithForward(target),
Properties: properties,
}
var installedRoute fnetRoutes.InstalledRouteV4
installedRoute.SetRoute(innerRoute)
installedRoute.SetEffectiveProperties(effectiveProperties)
return InstalledRoute{
Version: routetypes.IPv4,
V4: installedRoute,
}
case net.IpAddressIpv6:
destination := net.Ipv6AddressWithPrefix{
Addr: dst.Ipv6,
PrefixLen: uint8(route.Route.Destination.Prefix()),
}
target := fnetRoutes.RouteTargetV6{
OutboundInterface: uint64(route.Route.NIC),
}
if route.Route.Gateway.Len() != 0 {
gateway := ToNetIpAddress(route.Route.Gateway)
if gateway.Which() != net.IpAddressIpv6 {
panic(fmt.Sprintf(
"Route with IPv6 Destination and non-IPv6 Gateway: %s",
route.Route,
))
}
target.NextHop = &gateway.Ipv6
}
var properties fnetRoutes.RoutePropertiesV6
properties.SetSpecifiedProperties(specifiedProperties)
innerRoute := fnetRoutes.RouteV6{
Destination: destination,
Action: fnetRoutes.RouteActionV6WithForward(target),
Properties: properties,
}
var installedRoute fnetRoutes.InstalledRouteV6
installedRoute.SetRoute(innerRoute)
installedRoute.SetEffectiveProperties(effectiveProperties)
return InstalledRoute{
Version: routetypes.IPv6,
V6: installedRoute,
}
default:
panic(fmt.Sprintf("invalid IP protocol for address: I_ipAddressTag=%d", dst.I_ipAddressTag))
}
}
type RouteComparisonKey struct {
V4 Route[net.Ipv4Address]
V6 Route[net.Ipv6Address]
}
// Converts the given `InstalledRoute` to a `RouteComparisonKey` such that if
// this route's `RouteComparisonKey` is equal to another route's
// `RouteComparisonKey`, the two routes should be considered identical for the
// purposes of adding or removing a matching route in the routing table.
func (installedRoute InstalledRoute) ToRouteComparisonKey() (RouteComparisonKey, error) {
var key RouteComparisonKey
switch installedRoute.Version {
case routetypes.IPv4:
route, validationResult := FromFidlRouteV4(installedRoute.V4.GetRoute())
if err := validationResult.ToError(); err != nil {
return key, err
}
key.V4 = route
return key, nil
case routetypes.IPv6:
route, validationResult := FromFidlRouteV6(installedRoute.V6.GetRoute())
if err := validationResult.ToError(); err != nil {
return key, err
}
key.V6 = route
return key, nil
default:
panic("unknown IpProtoTag")
}
}
// Converts the given `ExtendedRoute` to a `RouteComparisonKey` such that if
// this route's `RouteComparisonKey` is equal to another route's
// `RouteComparisonKey`, the two routes should be considered identical for the
// purposes of adding or removing a matching route in the routing table.
func ToRouteComparisonKey(route routetypes.ExtendedRoute) (RouteComparisonKey, error) {
return ToInstalledRoute(route).ToRouteComparisonKey()
}