2024-04-03 18:04:22 +02:00
|
|
|
//go:build !android && !ios
|
|
|
|
|
|
|
|
package routemanager
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/netip"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/libp2p/go-netroute"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
|
|
"github.com/netbirdio/netbird/iface"
|
|
|
|
nbnet "github.com/netbirdio/netbird/util/net"
|
|
|
|
)
|
|
|
|
|
|
|
|
var splitDefaultv4_1 = netip.PrefixFrom(netip.IPv4Unspecified(), 1)
|
|
|
|
var splitDefaultv4_2 = netip.PrefixFrom(netip.AddrFrom4([4]byte{128}), 1)
|
|
|
|
var splitDefaultv6_1 = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
|
|
|
|
var splitDefaultv6_2 = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x80}), 1)
|
|
|
|
|
2024-04-03 19:06:04 +02:00
|
|
|
var ErrRouteNotFound = errors.New("route not found")
|
|
|
|
var ErrRouteNotAllowed = errors.New("route not allowed")
|
2024-04-03 18:04:22 +02:00
|
|
|
|
|
|
|
// TODO: fix: for default our wg address now appears as the default gw
|
|
|
|
func addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
|
|
|
addr := netip.IPv4Unspecified()
|
|
|
|
if prefix.Addr().Is6() {
|
|
|
|
addr = netip.IPv6Unspecified()
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultGateway, _, err := getNextHop(addr)
|
2024-04-03 19:06:04 +02:00
|
|
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
2024-04-03 18:04:22 +02:00
|
|
|
return fmt.Errorf("get existing route gateway: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !prefix.Contains(defaultGateway) {
|
|
|
|
log.Debugf("Skipping adding a new route for gateway %s because it is not in the network %s", defaultGateway, prefix)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
gatewayPrefix := netip.PrefixFrom(defaultGateway, 32)
|
|
|
|
if defaultGateway.Is6() {
|
|
|
|
gatewayPrefix = netip.PrefixFrom(defaultGateway, 128)
|
|
|
|
}
|
|
|
|
|
|
|
|
ok, err := existsInRouteTable(gatewayPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to check if there is an existing route for gateway %s. error: %s", gatewayPrefix, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
log.Debugf("Skipping adding a new route for gateway %s because it already exists", gatewayPrefix)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var exitIntf string
|
|
|
|
gatewayHop, intf, err := getNextHop(defaultGateway)
|
2024-04-03 19:06:04 +02:00
|
|
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
2024-04-03 18:04:22 +02:00
|
|
|
return fmt.Errorf("unable to get the next hop for the default gateway address. error: %s", err)
|
|
|
|
}
|
|
|
|
if intf != nil {
|
|
|
|
exitIntf = intf.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Adding a new route for gateway %s with next hop %s", gatewayPrefix, gatewayHop)
|
|
|
|
return addToRouteTable(gatewayPrefix, gatewayHop, exitIntf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getNextHop(ip netip.Addr) (netip.Addr, *net.Interface, error) {
|
|
|
|
r, err := netroute.New()
|
|
|
|
if err != nil {
|
|
|
|
return netip.Addr{}, nil, fmt.Errorf("new netroute: %w", err)
|
|
|
|
}
|
|
|
|
intf, gateway, preferredSrc, err := r.Route(ip.AsSlice())
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Failed to get route for %s: %v", ip, err)
|
2024-04-03 19:06:04 +02:00
|
|
|
return netip.Addr{}, nil, ErrRouteNotFound
|
2024-04-03 18:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Route for %s: interface %v, nexthop %v, preferred source %v", ip, intf, gateway, preferredSrc)
|
|
|
|
if gateway == nil {
|
|
|
|
if preferredSrc == nil {
|
2024-04-03 19:06:04 +02:00
|
|
|
return netip.Addr{}, nil, ErrRouteNotFound
|
2024-04-03 18:04:22 +02:00
|
|
|
}
|
|
|
|
log.Debugf("No next hop found for ip %s, using preferred source %s", ip, preferredSrc)
|
|
|
|
|
|
|
|
addr, ok := netip.AddrFromSlice(preferredSrc)
|
|
|
|
if !ok {
|
|
|
|
return netip.Addr{}, nil, fmt.Errorf("failed to parse IP address: %s", preferredSrc)
|
|
|
|
}
|
|
|
|
return addr.Unmap(), intf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, ok := netip.AddrFromSlice(gateway)
|
|
|
|
if !ok {
|
|
|
|
return netip.Addr{}, nil, fmt.Errorf("failed to parse IP address: %s", gateway)
|
|
|
|
}
|
|
|
|
|
|
|
|
return addr.Unmap(), intf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func existsInRouteTable(prefix netip.Prefix) (bool, error) {
|
|
|
|
routes, err := getRoutesFromTable()
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("get routes from table: %w", err)
|
|
|
|
}
|
|
|
|
for _, tableRoute := range routes {
|
|
|
|
if tableRoute == prefix {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isSubRange(prefix netip.Prefix) (bool, error) {
|
|
|
|
routes, err := getRoutesFromTable()
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("get routes from table: %w", err)
|
|
|
|
}
|
|
|
|
for _, tableRoute := range routes {
|
|
|
|
if tableRoute.Bits() > minRangeBits && tableRoute.Contains(prefix.Addr()) && tableRoute.Bits() < prefix.Bits() {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 19:06:04 +02:00
|
|
|
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
|
|
|
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
2024-04-03 18:04:22 +02:00
|
|
|
func addRouteToNonVPNIntf(
|
|
|
|
prefix netip.Prefix,
|
|
|
|
vpnIntf *iface.WGIface,
|
|
|
|
initialNextHop netip.Addr,
|
|
|
|
initialIntf *net.Interface,
|
|
|
|
) (netip.Addr, string, error) {
|
|
|
|
addr := prefix.Addr()
|
|
|
|
switch {
|
2024-04-03 19:06:04 +02:00
|
|
|
case addr.IsLoopback(),
|
|
|
|
addr.IsLinkLocalUnicast(),
|
|
|
|
addr.IsLinkLocalMulticast(),
|
|
|
|
addr.IsInterfaceLocalMulticast(),
|
|
|
|
addr.IsUnspecified(),
|
|
|
|
addr.IsMulticast():
|
|
|
|
|
|
|
|
return netip.Addr{}, "", ErrRouteNotAllowed
|
2024-04-03 18:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the exit interface and next hop for the prefix, so we can add a specific route
|
|
|
|
nexthop, intf, err := getNextHop(addr)
|
|
|
|
if err != nil {
|
|
|
|
return netip.Addr{}, "", fmt.Errorf("get next hop: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Found next hop %s for prefix %s with interface %v", nexthop, prefix, intf)
|
|
|
|
exitNextHop := nexthop
|
|
|
|
var exitIntf string
|
|
|
|
if intf != nil {
|
|
|
|
exitIntf = intf.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
vpnAddr, ok := netip.AddrFromSlice(vpnIntf.Address().IP)
|
|
|
|
if !ok {
|
|
|
|
return netip.Addr{}, "", fmt.Errorf("failed to convert vpn address to netip.Addr")
|
|
|
|
}
|
|
|
|
|
|
|
|
// if next hop is the VPN address or the interface is the VPN interface, we should use the initial values
|
|
|
|
if exitNextHop == vpnAddr || exitIntf == vpnIntf.Name() {
|
|
|
|
log.Debugf("Route for prefix %s is pointing to the VPN interface", prefix)
|
|
|
|
exitNextHop = initialNextHop
|
|
|
|
if initialIntf != nil {
|
|
|
|
exitIntf = initialIntf.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Adding a new route for prefix %s with next hop %s", prefix, exitNextHop)
|
|
|
|
if err := addToRouteTable(prefix, exitNextHop, exitIntf); err != nil {
|
|
|
|
return netip.Addr{}, "", fmt.Errorf("add route to table: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return exitNextHop, exitIntf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// genericAddVPNRoute adds a new route to the vpn interface, it splits the default prefix
|
|
|
|
// in two /1 prefixes to avoid replacing the existing default route
|
|
|
|
func genericAddVPNRoute(prefix netip.Prefix, intf string) error {
|
|
|
|
if prefix == defaultv4 {
|
|
|
|
if err := addToRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := addToRouteTable(splitDefaultv4_2, netip.Addr{}, intf); err != nil {
|
|
|
|
if err2 := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err2 != nil {
|
|
|
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: remove once IPv6 is supported on the interface
|
|
|
|
if err := addToRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
|
|
|
return fmt.Errorf("add unreachable route split 1: %w", err)
|
|
|
|
}
|
|
|
|
if err := addToRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
|
|
|
if err2 := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err2 != nil {
|
|
|
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("add unreachable route split 2: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
} else if prefix == defaultv6 {
|
|
|
|
if err := addToRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
|
|
|
return fmt.Errorf("add unreachable route split 1: %w", err)
|
|
|
|
}
|
|
|
|
if err := addToRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
|
|
|
if err2 := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err2 != nil {
|
|
|
|
log.Warnf("Failed to rollback route addition: %s", err2)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("add unreachable route split 2: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return addNonExistingRoute(prefix, intf)
|
|
|
|
}
|
|
|
|
|
|
|
|
// addNonExistingRoute adds a new route to the vpn interface if it doesn't exist in the current routing table
|
|
|
|
func addNonExistingRoute(prefix netip.Prefix, intf string) error {
|
|
|
|
ok, err := existsInRouteTable(prefix)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("exists in route table: %w", err)
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
log.Warnf("Skipping adding a new route for network %s because it already exists", prefix)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ok, err = isSubRange(prefix)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("sub range: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
err := addRouteForCurrentDefaultGateway(prefix)
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Unable to add route for current default gateway route. Will proceed without it. error: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return addToRouteTable(prefix, netip.Addr{}, intf)
|
|
|
|
}
|
|
|
|
|
|
|
|
// genericRemoveVPNRoute removes the route from the vpn interface. If a default prefix is given,
|
|
|
|
// it will remove the split /1 prefixes
|
|
|
|
func genericRemoveVPNRoute(prefix netip.Prefix, intf string) error {
|
|
|
|
if prefix == defaultv4 {
|
|
|
|
var result *multierror.Error
|
|
|
|
if err := removeFromRouteTable(splitDefaultv4_1, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
if err := removeFromRouteTable(splitDefaultv4_2, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: remove once IPv6 is supported on the interface
|
|
|
|
if err := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
if err := removeFromRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.ErrorOrNil()
|
|
|
|
} else if prefix == defaultv6 {
|
|
|
|
var result *multierror.Error
|
|
|
|
if err := removeFromRouteTable(splitDefaultv6_1, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
if err := removeFromRouteTable(splitDefaultv6_2, netip.Addr{}, intf); err != nil {
|
|
|
|
result = multierror.Append(result, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.ErrorOrNil()
|
|
|
|
}
|
|
|
|
|
|
|
|
return removeFromRouteTable(prefix, netip.Addr{}, intf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPrefixFromIP(ip net.IP) (*netip.Prefix, error) {
|
|
|
|
addr, ok := netip.AddrFromSlice(ip)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("parse IP address: %s", ip)
|
|
|
|
}
|
|
|
|
addr = addr.Unmap()
|
|
|
|
|
|
|
|
var prefixLength int
|
|
|
|
switch {
|
|
|
|
case addr.Is4():
|
|
|
|
prefixLength = 32
|
|
|
|
case addr.Is6():
|
|
|
|
prefixLength = 128
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("invalid IP address: %s", addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
prefix := netip.PrefixFrom(addr, prefixLength)
|
|
|
|
return &prefix, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupRoutingWithRouteManager(routeManager **RouteManager, initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
|
|
|
initialNextHopV4, initialIntfV4, err := getNextHop(netip.IPv4Unspecified())
|
2024-04-03 19:06:04 +02:00
|
|
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
2024-04-03 18:04:22 +02:00
|
|
|
log.Errorf("Unable to get initial v4 default next hop: %v", err)
|
|
|
|
}
|
|
|
|
initialNextHopV6, initialIntfV6, err := getNextHop(netip.IPv6Unspecified())
|
2024-04-03 19:06:04 +02:00
|
|
|
if err != nil && !errors.Is(err, ErrRouteNotFound) {
|
2024-04-03 18:04:22 +02:00
|
|
|
log.Errorf("Unable to get initial v6 default next hop: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
*routeManager = NewRouteManager(
|
|
|
|
func(prefix netip.Prefix) (netip.Addr, string, error) {
|
|
|
|
addr := prefix.Addr()
|
|
|
|
nexthop, intf := initialNextHopV4, initialIntfV4
|
|
|
|
if addr.Is6() {
|
|
|
|
nexthop, intf = initialNextHopV6, initialIntfV6
|
|
|
|
}
|
|
|
|
return addRouteToNonVPNIntf(prefix, wgIface, nexthop, intf)
|
|
|
|
},
|
|
|
|
removeFromRouteTable,
|
|
|
|
)
|
|
|
|
|
|
|
|
return setupHooks(*routeManager, initAddresses)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanupRoutingWithRouteManager(routeManager *RouteManager) error {
|
|
|
|
if routeManager == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Remove hooks selectively
|
|
|
|
nbnet.RemoveDialerHooks()
|
|
|
|
nbnet.RemoveListenerHooks()
|
|
|
|
|
|
|
|
if err := routeManager.Flush(); err != nil {
|
|
|
|
return fmt.Errorf("flush route manager: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupHooks(routeManager *RouteManager, initAddresses []net.IP) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
|
|
|
beforeHook := func(connID nbnet.ConnectionID, ip net.IP) error {
|
|
|
|
prefix, err := getPrefixFromIP(ip)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("convert ip to prefix: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := routeManager.AddRouteRef(connID, *prefix); err != nil {
|
|
|
|
return fmt.Errorf("adding route reference: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
afterHook := func(connID nbnet.ConnectionID) error {
|
|
|
|
if err := routeManager.RemoveRouteRef(connID); err != nil {
|
|
|
|
return fmt.Errorf("remove route reference: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ip := range initAddresses {
|
|
|
|
if err := beforeHook("init", ip); err != nil {
|
|
|
|
log.Errorf("Failed to add route reference: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nbnet.AddDialerHook(func(ctx context.Context, connID nbnet.ConnectionID, resolvedIPs []net.IPAddr) error {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
var result *multierror.Error
|
|
|
|
for _, ip := range resolvedIPs {
|
|
|
|
result = multierror.Append(result, beforeHook(connID, ip.IP))
|
|
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
|
|
})
|
|
|
|
|
|
|
|
nbnet.AddDialerCloseHook(func(connID nbnet.ConnectionID, conn *net.Conn) error {
|
|
|
|
return afterHook(connID)
|
|
|
|
})
|
|
|
|
|
|
|
|
nbnet.AddListenerWriteHook(func(connID nbnet.ConnectionID, ip *net.IPAddr, data []byte) error {
|
|
|
|
return beforeHook(connID, ip.IP)
|
|
|
|
})
|
|
|
|
|
|
|
|
nbnet.AddListenerCloseHook(func(connID nbnet.ConnectionID, conn net.PacketConn) error {
|
|
|
|
return afterHook(connID)
|
|
|
|
})
|
|
|
|
|
|
|
|
return beforeHook, afterHook, nil
|
|
|
|
}
|