2023-06-09 18:27:09 +02:00
|
|
|
//go:build windows
|
|
|
|
|
|
|
|
package routemanager
|
2023-06-09 19:15:39 +02:00
|
|
|
|
|
|
|
import (
|
2024-04-08 18:56:52 +02:00
|
|
|
"fmt"
|
2023-06-12 11:43:18 +02:00
|
|
|
"net"
|
2023-06-09 19:15:39 +02:00
|
|
|
"net/netip"
|
2024-04-08 18:56:52 +02:00
|
|
|
"os/exec"
|
|
|
|
"strings"
|
2023-06-09 19:15:39 +02:00
|
|
|
|
2024-04-08 18:56:52 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
2023-06-09 19:17:26 +02:00
|
|
|
"github.com/yusufpapurcu/wmi"
|
2024-04-08 18:56:52 +02:00
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
|
|
"github.com/netbirdio/netbird/iface"
|
2023-06-09 19:15:39 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Win32_IP4RouteTable struct {
|
2023-06-12 11:06:49 +02:00
|
|
|
Destination string
|
|
|
|
Mask string
|
2023-06-09 19:15:39 +02:00
|
|
|
}
|
|
|
|
|
2024-04-08 18:56:52 +02:00
|
|
|
var routeManager *RouteManager
|
|
|
|
|
|
|
|
func setupRouting(initAddresses []net.IP, wgIface *iface.WGIface) (peer.BeforeAddPeerHookFunc, peer.AfterRemovePeerHookFunc, error) {
|
|
|
|
return setupRoutingWithRouteManager(&routeManager, initAddresses, wgIface)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanupRouting() error {
|
|
|
|
return cleanupRoutingWithRouteManager(routeManager)
|
|
|
|
}
|
|
|
|
|
2023-11-24 11:31:22 +01:00
|
|
|
func getRoutesFromTable() ([]netip.Prefix, error) {
|
2023-06-09 19:15:39 +02:00
|
|
|
var routes []Win32_IP4RouteTable
|
2023-06-12 16:22:53 +02:00
|
|
|
query := "SELECT Destination, Mask FROM Win32_IP4RouteTable"
|
2023-06-09 19:15:39 +02:00
|
|
|
|
|
|
|
err := wmi.Query(query, &routes)
|
|
|
|
if err != nil {
|
2024-04-08 18:56:52 +02:00
|
|
|
return nil, fmt.Errorf("get routes: %w", err)
|
2023-06-09 19:15:39 +02:00
|
|
|
}
|
|
|
|
|
2023-11-24 11:31:22 +01:00
|
|
|
var prefixList []netip.Prefix
|
2023-06-09 19:15:39 +02:00
|
|
|
for _, route := range routes {
|
2023-11-24 11:31:22 +01:00
|
|
|
addr, err := netip.ParseAddr(route.Destination)
|
|
|
|
if err != nil {
|
2024-04-08 18:56:52 +02:00
|
|
|
log.Warnf("Unable to parse route destination %s: %v", route.Destination, err)
|
2023-11-24 11:31:22 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
maskSlice := net.ParseIP(route.Mask).To4()
|
|
|
|
if maskSlice == nil {
|
2024-04-08 18:56:52 +02:00
|
|
|
log.Warnf("Unable to parse route mask %s", route.Mask)
|
2023-11-24 11:31:22 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mask := net.IPv4Mask(maskSlice[0], maskSlice[1], maskSlice[2], maskSlice[3])
|
2023-06-12 11:43:18 +02:00
|
|
|
cidr, _ := mask.Size()
|
2023-11-24 11:31:22 +01:00
|
|
|
|
|
|
|
routePrefix := netip.PrefixFrom(addr, cidr)
|
|
|
|
if routePrefix.IsValid() && routePrefix.Addr().Is4() {
|
|
|
|
prefixList = append(prefixList, routePrefix)
|
2023-06-12 11:43:18 +02:00
|
|
|
}
|
2023-06-09 19:15:39 +02:00
|
|
|
}
|
2023-11-24 11:31:22 +01:00
|
|
|
return prefixList, nil
|
2023-06-09 19:15:39 +02:00
|
|
|
}
|
2024-04-08 18:56:52 +02:00
|
|
|
|
2024-04-09 13:25:14 +02:00
|
|
|
func addRoutePowershell(prefix netip.Prefix, nexthop netip.Addr, intf, intfIdx string) error {
|
2024-04-08 18:56:52 +02:00
|
|
|
destinationPrefix := prefix.String()
|
|
|
|
psCmd := "New-NetRoute"
|
|
|
|
|
|
|
|
addressFamily := "IPv4"
|
|
|
|
if prefix.Addr().Is6() {
|
|
|
|
addressFamily = "IPv6"
|
|
|
|
}
|
|
|
|
|
|
|
|
script := fmt.Sprintf(
|
2024-04-09 13:25:14 +02:00
|
|
|
`%s -AddressFamily "%s" -DestinationPrefix "%s" -Confirm:$False -ErrorAction Stop`,
|
|
|
|
psCmd, addressFamily, destinationPrefix,
|
2024-04-08 18:56:52 +02:00
|
|
|
)
|
|
|
|
|
2024-04-09 13:25:14 +02:00
|
|
|
if intfIdx != "" {
|
|
|
|
script = fmt.Sprintf(
|
|
|
|
`%s -InterfaceIndex %s`, script, intfIdx,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
script = fmt.Sprintf(
|
|
|
|
`%s -InterfaceAlias "%s"`, script, intf,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-04-08 18:56:52 +02:00
|
|
|
if nexthop.IsValid() {
|
|
|
|
script = fmt.Sprintf(
|
|
|
|
`%s -NextHop "%s"`, script, nexthop,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command("powershell", "-Command", script).CombinedOutput()
|
2024-04-09 13:25:14 +02:00
|
|
|
log.Tracef("PowerShell %s: %s", script, string(out))
|
2024-04-08 18:56:52 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("PowerShell add route: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addRouteCmd(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
|
|
|
args := []string{"add", prefix.String(), nexthop.Unmap().String()}
|
|
|
|
|
|
|
|
out, err := exec.Command("route", args...).CombinedOutput()
|
|
|
|
|
2024-04-09 13:25:14 +02:00
|
|
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
2024-04-08 18:56:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("route add: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addToRouteTable(prefix netip.Prefix, nexthop netip.Addr, intf string) error {
|
2024-04-09 13:25:14 +02:00
|
|
|
var intfIdx string
|
|
|
|
if nexthop.Zone() != "" {
|
|
|
|
intfIdx = nexthop.Zone()
|
|
|
|
nexthop.WithZone("")
|
|
|
|
}
|
|
|
|
|
2024-04-08 18:56:52 +02:00
|
|
|
// Powershell doesn't support adding routes without an interface but allows to add interface by name
|
2024-04-09 13:25:14 +02:00
|
|
|
if intf != "" || intfIdx != "" {
|
|
|
|
return addRoutePowershell(prefix, nexthop, intf, intfIdx)
|
2024-04-08 18:56:52 +02:00
|
|
|
}
|
|
|
|
return addRouteCmd(prefix, nexthop, intf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeFromRouteTable(prefix netip.Prefix, nexthop netip.Addr, _ string) error {
|
|
|
|
args := []string{"delete", prefix.String()}
|
|
|
|
if nexthop.IsValid() {
|
2024-04-09 13:25:14 +02:00
|
|
|
nexthop.WithZone("")
|
2024-04-08 18:56:52 +02:00
|
|
|
args = append(args, nexthop.Unmap().String())
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command("route", args...).CombinedOutput()
|
2024-04-09 13:25:14 +02:00
|
|
|
log.Tracef("route %s: %s", strings.Join(args, " "), out)
|
2024-04-08 18:56:52 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("remove route: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|