Browse Source

Merge pull request #1482 from gravitl/feature_v0.14.7_explicit_routes

Feature v0.14.7 explicit routes
Alex Feiszli 3 years ago
parent
commit
9170001ef3

+ 4 - 7
controllers/ipservice.go

@@ -7,6 +7,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
 func ipHandlers(r *mux.Router) {
 func ipHandlers(r *mux.Router) {
@@ -38,7 +39,7 @@ func parseIP(r *http.Request) (string, error) {
 	// Get Public IP from header
 	// Get Public IP from header
 	ip := r.Header.Get("X-REAL-IP")
 	ip := r.Header.Get("X-REAL-IP")
 	ipnet := net.ParseIP(ip)
 	ipnet := net.ParseIP(ip)
-	if ipnet != nil && !ipIsPrivate(ipnet) {
+	if ipnet != nil && !ncutils.IpIsPrivate(ipnet) {
 		return ip, nil
 		return ip, nil
 	}
 	}
 
 
@@ -47,7 +48,7 @@ func parseIP(r *http.Request) (string, error) {
 	iplist := strings.Split(forwardips, ",")
 	iplist := strings.Split(forwardips, ",")
 	for _, ip := range iplist {
 	for _, ip := range iplist {
 		ipnet := net.ParseIP(ip)
 		ipnet := net.ParseIP(ip)
-		if ipnet != nil && !ipIsPrivate(ipnet) {
+		if ipnet != nil && !ncutils.IpIsPrivate(ipnet) {
 			return ip, nil
 			return ip, nil
 		}
 		}
 	}
 	}
@@ -59,14 +60,10 @@ func parseIP(r *http.Request) (string, error) {
 	}
 	}
 	ipnet = net.ParseIP(ip)
 	ipnet = net.ParseIP(ip)
 	if ipnet != nil {
 	if ipnet != nil {
-		if ipIsPrivate(ipnet) {
+		if ncutils.IpIsPrivate(ipnet) {
 			return ip, fmt.Errorf("ip is a private address")
 			return ip, fmt.Errorf("ip is a private address")
 		}
 		}
 		return ip, nil
 		return ip, nil
 	}
 	}
 	return "", fmt.Errorf("no ip found")
 	return "", fmt.Errorf("no ip found")
 }
 }
-
-func ipIsPrivate(ipnet net.IP) bool {
-	return ipnet.IsPrivate() || ipnet.IsLoopback()
-}

+ 2 - 2
logic/nodes.go

@@ -185,7 +185,7 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error {
 		// ignoring for now, could hit a nil pointer if delete called twice
 		// ignoring for now, could hit a nil pointer if delete called twice
 		logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID)
 		logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID)
 	}
 	}
-	removeZombie <- node.ID
+	// removeZombie <- node.ID
 	if node.IsServer == "yes" {
 	if node.IsServer == "yes" {
 		return removeLocalServer(node)
 		return removeLocalServer(node)
 	}
 	}
@@ -288,7 +288,7 @@ func CreateNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	CheckZombies(node)
+	// CheckZombies(node)
 
 
 	nodebytes, err := json.Marshal(&node)
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {

+ 1 - 1
logic/peers.go

@@ -271,7 +271,7 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 		egressIPs := getEgressIPs(node, peer)
 		egressIPs := getEgressIPs(node, peer)
 		// remove internet gateway if server
 		// remove internet gateway if server
 		if node.IsServer == "yes" {
 		if node.IsServer == "yes" {
-			for i := len(egressIPs) - 1; i <= 0; i-- {
+			for i := len(egressIPs) - 1; i >= 0; i-- {
 				if egressIPs[i].IP.String() == "0.0.0.0/0" || egressIPs[i].IP.String() == "::/0" {
 				if egressIPs[i].IP.String() == "0.0.0.0/0" || egressIPs[i].IP.String() == "::/0" {
 					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
 					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
 				}
 				}

+ 4 - 4
logic/zombie.go

@@ -17,8 +17,8 @@ const (
 
 
 var (
 var (
 	zombies      []string
 	zombies      []string
-	removeZombie chan string = make(chan (string))
-	newZombie    chan string = make(chan (string))
+	removeZombie chan string = make(chan (string), 10)
+	newZombie    chan string = make(chan (string), 10)
 )
 )
 
 
 // CheckZombies - checks if new node has same macaddress as existing node
 // CheckZombies - checks if new node has same macaddress as existing node
@@ -48,7 +48,7 @@ func ManageZombies(ctx context.Context) {
 		case id := <-removeZombie:
 		case id := <-removeZombie:
 			found := false
 			found := false
 			if len(zombies) > 0 {
 			if len(zombies) > 0 {
-				for i := len(zombies) - 1; i <= 0; i-- {
+				for i := len(zombies) - 1; i >= 0; i-- {
 					if zombies[i] == id {
 					if zombies[i] == id {
 						logger.Log(1, "removing zombie from quaratine list", zombies[i])
 						logger.Log(1, "removing zombie from quaratine list", zombies[i])
 						zombies = append(zombies[:i], zombies[i+1:]...)
 						zombies = append(zombies[:i], zombies[i+1:]...)
@@ -61,7 +61,7 @@ func ManageZombies(ctx context.Context) {
 			}
 			}
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 			if len(zombies) > 0 {
 			if len(zombies) > 0 {
-				for i := len(zombies) - 1; i <= 0; i-- {
+				for i := len(zombies) - 1; i >= 0; i-- {
 					node, err := GetNodeByID(zombies[i])
 					node, err := GetNodeByID(zombies[i])
 					if err != nil {
 					if err != nil {
 						logger.Log(1, "error retrieving zombie node", zombies[i], err.Error())
 						logger.Log(1, "error retrieving zombie node", zombies[i], err.Error())

+ 12 - 5
netclient/functions/common.go

@@ -30,6 +30,16 @@ const LINUX_APP_DATA_PATH = "/etc/netmaker"
 // HTTP_TIMEOUT - timeout in seconds for http requests
 // HTTP_TIMEOUT - timeout in seconds for http requests
 const HTTP_TIMEOUT = 30
 const HTTP_TIMEOUT = 30
 
 
+// HTTPClient - http client to be reused by all
+var HTTPClient http.Client
+
+// SetHTTPClient -sets http client with sane default
+func SetHTTPClient() {
+	HTTPClient = http.Client{
+		Timeout: HTTP_TIMEOUT * time.Second,
+	}
+}
+
 // ListPorts - lists ports of WireGuard devices
 // ListPorts - lists ports of WireGuard devices
 func ListPorts() error {
 func ListPorts() error {
 	wgclient, err := wgctrl.New()
 	wgclient, err := wgctrl.New()
@@ -308,7 +318,7 @@ func GetNetmakerPath() string {
 	return LINUX_APP_DATA_PATH
 	return LINUX_APP_DATA_PATH
 }
 }
 
 
-//API function to interact with netmaker api endpoints. response from endpoint is returned
+// API function to interact with netmaker api endpoints. response from endpoint is returned
 func API(data any, method, url, authorization string) (*http.Response, error) {
 func API(data any, method, url, authorization string) (*http.Response, error) {
 	var request *http.Request
 	var request *http.Request
 	var err error
 	var err error
@@ -331,10 +341,7 @@ func API(data any, method, url, authorization string) (*http.Response, error) {
 	if authorization != "" {
 	if authorization != "" {
 		request.Header.Set("authorization", "Bearer "+authorization)
 		request.Header.Set("authorization", "Bearer "+authorization)
 	}
 	}
-	client := http.Client{
-		Timeout: HTTP_TIMEOUT * time.Second,
-	}
-	return client.Do(request)
+	return HTTPClient.Do(request)
 }
 }
 
 
 // Authenticate authenticates with api to permit subsequent interactions with the api
 // Authenticate authenticates with api to permit subsequent interactions with the api

+ 4 - 0
netclient/functions/daemon.go

@@ -106,6 +106,10 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc {
 			// == subscribe to all nodes for each on machine ==
 			// == subscribe to all nodes for each on machine ==
 			serverSet[server] = true
 			serverSet[server] = true
 			logger.Log(1, "started daemon for server ", server)
 			logger.Log(1, "started daemon for server ", server)
+			err := local.SetNetmakerDomainRoute(cfg.Server.API)
+			if err != nil {
+				logger.Log(0, "error setting route for netmaker: "+err.Error())
+			}
 			wg.Add(1)
 			wg.Add(1)
 			go messageQueue(ctx, wg, &cfg)
 			go messageQueue(ctx, wg, &cfg)
 		}
 		}

+ 12 - 6
netclient/functions/join.go

@@ -118,10 +118,10 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 	}
 	}
 
 
 	if cfg.Node.FirewallInUse == "" {
 	if cfg.Node.FirewallInUse == "" {
-		if ncutils.IsNFTablesPresent() {
-			cfg.Node.FirewallInUse = models.FIREWALL_NFTABLES
-		} else if ncutils.IsIPTablesPresent() {
+		if ncutils.IsIPTablesPresent() {
 			cfg.Node.FirewallInUse = models.FIREWALL_IPTABLES
 			cfg.Node.FirewallInUse = models.FIREWALL_IPTABLES
+		} else if ncutils.IsNFTablesPresent() {
+			cfg.Node.FirewallInUse = models.FIREWALL_NFTABLES
 		} else {
 		} else {
 			cfg.Node.FirewallInUse = models.FIREWALL_NONE
 			cfg.Node.FirewallInUse = models.FIREWALL_NONE
 		}
 		}
@@ -200,15 +200,21 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 	if err = config.SaveBackup(node.Network); err != nil {
 	if err = config.SaveBackup(node.Network); err != nil {
 		logger.Log(0, "network:", node.Network, "failed to make backup, node will not auto restore if config is corrupted")
 		logger.Log(0, "network:", node.Network, "failed to make backup, node will not auto restore if config is corrupted")
 	}
 	}
-	logger.Log(0, "starting wireguard")
-	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:], false)
+
+	err = local.SetNetmakerDomainRoute(cfg.Server.API)
 	if err != nil {
 	if err != nil {
-		return err
+		logger.Log(0, "error setting route for netmaker: "+err.Error())
 	}
 	}
 	cfg.Node = node
 	cfg.Node = node
 	if err := Register(cfg); err != nil {
 	if err := Register(cfg); err != nil {
 		return err
 		return err
 	}
 	}
+
+	logger.Log(0, "starting wireguard")
+	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:], false)
+	if err != nil {
+		return err
+	}
 	if cfg.Server.Server == "" {
 	if cfg.Server.Server == "" {
 		return errors.New("did not receive broker address from registration")
 		return errors.New("did not receive broker address from registration")
 	}
 	}

+ 92 - 0
netclient/local/routes.go

@@ -1,7 +1,9 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"strings"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
@@ -12,6 +14,17 @@ import (
 
 
 // SetPeerRoutes - sets/removes ip routes for each peer on a network
 // SetPeerRoutes - sets/removes ip routes for each peer on a network
 func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.PeerConfig) {
 func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.PeerConfig) {
+
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
 	// traverse through all recieved peers
 	// traverse through all recieved peers
 	for _, peer := range newPeers {
 	for _, peer := range newPeers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
@@ -23,6 +36,16 @@ func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.Pe
 				delete(oldPeers, allowedIP.String())
 				delete(oldPeers, allowedIP.String())
 			}
 			}
 		}
 		}
+		if peer.Endpoint == nil {
+			continue
+		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			SetExplicitRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
 	// traverse through all remaining existing peers
 	// traverse through all remaining existing peers
 	for i := range oldPeers {
 	for i := range oldPeers {
@@ -37,19 +60,59 @@ func SetPeerRoutes(iface string, oldPeers map[string]bool, newPeers []wgtypes.Pe
 
 
 // SetCurrentPeerRoutes - sets all the current peers
 // SetCurrentPeerRoutes - sets all the current peers
 func SetCurrentPeerRoutes(iface, currentAddr string, peers []wgtypes.PeerConfig) {
 func SetCurrentPeerRoutes(iface, currentAddr string, peers []wgtypes.PeerConfig) {
+
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
+	// traverse through all recieved peers
 	for _, peer := range peers {
 	for _, peer := range peers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
 			setRoute(iface, &allowedIP, currentAddr)
 			setRoute(iface, &allowedIP, currentAddr)
 		}
 		}
+		if peer.Endpoint == nil {
+			continue
+		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			SetExplicitRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
+
 }
 }
 
 
 // FlushPeerRoutes - removes all current peer routes
 // FlushPeerRoutes - removes all current peer routes
 func FlushPeerRoutes(iface, currentAddr string, peers []wgtypes.Peer) {
 func FlushPeerRoutes(iface, currentAddr string, peers []wgtypes.Peer) {
+	// get the default route
+	var hasRoute bool
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		logger.Log(0, "error getting default route:", err.Error())
+	}
+	if gwIP != "" && gwIface != "" && err == nil {
+		hasRoute = true
+	}
+
 	for _, peer := range peers {
 	for _, peer := range peers {
 		for _, allowedIP := range peer.AllowedIPs {
 		for _, allowedIP := range peer.AllowedIPs {
 			deleteRoute(iface, &allowedIP, currentAddr)
 			deleteRoute(iface, &allowedIP, currentAddr)
 		}
 		}
+		if hasRoute && !ncutils.IpIsPrivate(peer.Endpoint.IP) {
+			ipNet, err := ncutils.GetIPNetFromString(peer.Endpoint.IP.String())
+			if err != nil {
+				logger.Log(0, "error parsing ip:", err.Error())
+			}
+			deleteRoute(gwIface, &ipNet, gwIP)
+		}
 	}
 	}
 }
 }
 
 
@@ -62,3 +125,32 @@ func SetCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 func RemoveCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 func RemoveCIDRRoute(iface, currentAddr string, cidr *net.IPNet) {
 	removeCidr(iface, cidr, currentAddr)
 	removeCidr(iface, cidr, currentAddr)
 }
 }
+
+// SetNetmakerDomainRoute - sets explicit route over Gateway for a given DNS name
+func SetNetmakerDomainRoute(domainRaw string) error {
+	parts := strings.Split(domainRaw, ":")
+	hostname := parts[0]
+	var address net.IPNet
+
+	gwIP, gwIface, err := GetDefaultRoute()
+	if err != nil {
+		return fmt.Errorf("error getting default route: %w", err)
+	}
+
+	ips, err := net.LookupIP(hostname)
+	if err != nil {
+		return err
+	}
+	for _, ip := range ips {
+		if ipv4 := ip.To4(); ipv4 != nil {
+			address, err = ncutils.GetIPNetFromString(ipv4.String())
+			if err == nil {
+				break
+			}
+		}
+	}
+	if err != nil || address.IP == nil {
+		return fmt.Errorf("address not found")
+	}
+	return SetExplicitRoute(gwIface, &address, gwIP)
+}

+ 40 - 3
netclient/local/routes_darwin.go

@@ -1,14 +1,46 @@
 package local
 package local
 
 
 import (
 import (
-	"net"
-	"strings"
-
+	"fmt"
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"net"
+	"regexp"
+	"strings"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a mac machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	var outLine string
+	output, err := ncutils.RunCmd("netstat -nr", false)
+	for _, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
+		if strings.Contains(line, "default") {
+			outLine = line
+			break
+		}
+	}
+	space := regexp.MustCompile(`\s+`)
+	outFormatted := space.ReplaceAllString(outLine, " ")
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outputSlice := strings.Split(string(outFormatted), " ")
+	if !strings.Contains(outputSlice[0], "default") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	ipaddr = outputSlice[1]
+	if err = ncutils.CheckIPAddress(ipaddr); err != nil {
+		return ipaddr, iface, err
+	}
+	iface = outputSlice[3]
+
+	return ipaddr, iface, err
+}
+
 // route -n add -net 10.0.0.0/8 192.168.0.254
 // route -n add -net 10.0.0.0/8 192.168.0.254
 // networksetup -setadditionalroutes Ethernet 192.168.1.0 255.255.255.0 10.0.0.2 persistent
 // networksetup -setadditionalroutes Ethernet 192.168.1.0 255.255.255.0 10.0.0.2 persistent
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
@@ -28,6 +60,11 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	return setRoute(iface, destination, gateway)
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route -q -n delete "+addr.String(), false)
 	_, err = ncutils.RunCmd("route -q -n delete "+addr.String(), false)

+ 35 - 2
netclient/local/routes_freebsd.go

@@ -1,16 +1,49 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"strings"
 
 
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
-func setRoute(iface string, addr *net.IPNet, address string) error {
+// GetDefaultRoute - Gets the default route (ip and interface) on a freebsd machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
 	var err error
 	var err error
-	_, _ = ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
+
+	output, err := ncutils.RunCmd("route show default", true)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outFormatted := strings.ReplaceAll(output, "\n", "")
+	if !strings.Contains(outFormatted, "default") && !strings.Contains(outFormatted, "interface:") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	outputSlice := strings.Split(string(outFormatted), " ")
+	for i, outString := range outputSlice {
+		if outString == "gateway:" {
+			ipaddr = outputSlice[i+1]
+		}
+		if outString == "interface:" {
+			iface = outputSlice[i+1]
+		}
+	}
+	return ipaddr, iface, err
+}
+
+func setRoute(iface string, addr *net.IPNet, address string) error {
+	_, err := ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
+	return err
+}
+
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	_, err := ncutils.RunCmd("route add "+destination.String()+" "+gateway, false)
 	return err
 	return err
 }
 }
 
 

+ 30 - 0
netclient/local/routes_linux.go

@@ -12,6 +12,30 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a linux machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	output, err := ncutils.RunCmd("ip route show default", false)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	outputSlice := strings.Split(output, " ")
+	if !strings.Contains(outputSlice[0], "default") {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	for i, outString := range outputSlice {
+		if outString == "via" {
+			ipaddr = outputSlice[i+1]
+		}
+		if outString == "dev" {
+			iface = outputSlice[i+1]
+		}
+	}
+	return ipaddr, iface, err
+}
+
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
 	out, err := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	out, err := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	if err != nil || !strings.Contains(out, iface) {
 	if err != nil || !strings.Contains(out, iface) {
@@ -20,6 +44,12 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	_, err := ncutils.RunCmd(fmt.Sprintf("ip route add %s via %s dev %s", destination.String(), gateway, iface), false)
+	return err
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	out, _ := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)
 	out, _ := ncutils.RunCmd(fmt.Sprintf("ip route get %s", addr.IP.String()), false)

+ 45 - 0
netclient/local/routes_windows.go

@@ -1,12 +1,48 @@
 package local
 package local
 
 
 import (
 import (
+	"fmt"
 	"net"
 	"net"
+	"regexp"
+	"strings"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 )
 
 
+// GetDefaultRoute - Gets the default route (ip and interface) on a windows machine
+func GetDefaultRoute() (string, string, error) {
+	var ipaddr string
+	var iface string
+	var err error
+	var outLine string
+	output, err := ncutils.RunCmd("netstat -rn", false)
+	if err != nil {
+		return ipaddr, iface, err
+	}
+	var startLook bool
+	for _, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
+		if strings.Contains(line, "Active Routes:") {
+			startLook = true
+		}
+		if startLook && strings.Contains(line, "0.0.0.0") {
+			outLine = line
+			break
+		}
+	}
+	if outLine == "" {
+		return ipaddr, iface, fmt.Errorf("could not find default gateway")
+	}
+	space := regexp.MustCompile(`\s+`)
+	outputSlice := strings.Split(strings.TrimSpace(space.ReplaceAllString(outLine, " ")), " ")
+	ipaddr = outputSlice[len(outputSlice)-3]
+	if err = ncutils.CheckIPAddress(ipaddr); err != nil {
+		return ipaddr, iface, fmt.Errorf("invalid output for ip address check: " + err.Error())
+	}
+	iface = "irrelevant"
+	return ipaddr, iface, err
+}
+
 func setRoute(iface string, addr *net.IPNet, address string) error {
 func setRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route ADD "+addr.String()+" "+address, false)
 	_, err = ncutils.RunCmd("route ADD "+addr.String()+" "+address, false)
@@ -15,6 +51,15 @@ func setRoute(iface string, addr *net.IPNet, address string) error {
 	return err
 	return err
 }
 }
 
 
+// SetExplicitRoute - sets route via explicit ip address
+func SetExplicitRoute(iface string, destination *net.IPNet, gateway string) error {
+	var err error
+	_, err = ncutils.RunCmd("route ADD "+destination.String()+" "+gateway, false)
+	time.Sleep(time.Second >> 2)
+	ncutils.RunCmd("route CHANGE "+destination.IP.String()+" MASK "+destination.Mask.String()+" "+gateway, false)
+	return err
+}
+
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 func deleteRoute(iface string, addr *net.IPNet, address string) error {
 	var err error
 	var err error
 	_, err = ncutils.RunCmd("route DELETE "+addr.IP.String()+" MASK "+addr.Mask.String()+" "+address, false)
 	_, err = ncutils.RunCmd("route DELETE "+addr.IP.String()+" MASK "+addr.Mask.String()+" "+address, false)

+ 2 - 0
netclient/main.go

@@ -10,6 +10,7 @@ import (
 
 
 	"github.com/gravitl/netmaker/netclient/cli_options"
 	"github.com/gravitl/netmaker/netclient/cli_options"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/functions"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncwindows"
 	"github.com/gravitl/netmaker/netclient/ncwindows"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
@@ -29,6 +30,7 @@ func main() {
 	app.UsageText = "netclient [global options] command [command options] [arguments...]. Adjust verbosity of given command with -v, -vv or -vvv (max)."
 	app.UsageText = "netclient [global options] command [command options] [arguments...]. Adjust verbosity of given command with -v, -vv or -vvv (max)."
 
 
 	setGarbageCollection()
 	setGarbageCollection()
+	functions.SetHTTPClient()
 
 
 	if ncutils.IsWindows() {
 	if ncutils.IsWindows() {
 		ncwindows.InitWindows()
 		ncwindows.InitWindows()

+ 4 - 0
netclient/ncutils/iface.go

@@ -90,3 +90,7 @@ func IfaceExists(ifacename string) bool {
 	}
 	}
 	return false
 	return false
 }
 }
+
+func IpIsPrivate(ipnet net.IP) bool {
+	return ipnet.IsPrivate() || ipnet.IsLoopback()
+}

+ 7 - 0
netclient/ncutils/netclientutils.go

@@ -355,6 +355,13 @@ func GetNetclientPathSpecific() string {
 	}
 	}
 }
 }
 
 
+func CheckIPAddress(ip string) error {
+	if net.ParseIP(ip) == nil {
+		return fmt.Errorf("ip address %s is invalid", ip)
+	}
+	return nil
+}
+
 // GetNewIface - Gets the name of the real interface created on Mac
 // GetNewIface - Gets the name of the real interface created on Mac
 func GetNewIface(dir string) (string, error) {
 func GetNewIface(dir string) (string, error) {
 	files, _ := os.ReadDir(dir)
 	files, _ := os.ReadDir(dir)

+ 1 - 0
netclient/wireguard/mac.go

@@ -49,6 +49,7 @@ func WgQuickUpMac(node *models.Node, iface string, confPath string) error {
 		return err
 		return err
 	}
 	}
 	time.Sleep(time.Second / 2)
 	time.Sleep(time.Second / 2)
+
 	err = setConfig(realIface, confPath)
 	err = setConfig(realIface, confPath)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "error setting config for ", realIface)
 		logger.Log(1, "error setting config for ", realIface)