Bläddra i källkod

Merge pull request #1030 from gravitl/feature_v0.13.0_ipv6_only

Feature v0.13.0 ipv6 only
dcarns 3 år sedan
förälder
incheckning
27cda5827d

+ 3 - 2
compose/docker-compose.contained.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.12.2
+    image: gravitl/netmaker:v0.13.0-dev
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -15,6 +15,7 @@ services:
     sysctls:
       - net.ipv4.ip_forward=1
       - net.ipv4.conf.all.src_valid_mark=1
+      - net.ipv6.conf.all.disable_ipv6=0
     restart: always
     environment:
       SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
@@ -42,7 +43,7 @@ services:
     container_name: netmaker-ui
     depends_on:
       - netmaker
-    image: gravitl/netmaker-ui:v0.12.2
+    image: gravitl/netmaker-ui:v0.13.0-dev
     links:
       - "netmaker:api"
     ports:

+ 31 - 31
controllers/dns_test.go

@@ -21,14 +21,14 @@ func TestGetAllDNS(t *testing.T) {
 		assert.Equal(t, []models.DNSEntry(nil), entries)
 	})
 	t.Run("OneEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
 		CreateDNS(entry)
 		entries, err := logic.GetAllDNS()
 		assert.Nil(t, err)
 		assert.Equal(t, 1, len(entries))
 	})
 	t.Run("MultipleEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.7", "anotherhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.7", "", "anotherhost", "skynet"}
 		CreateDNS(entry)
 		entries, err := logic.GetAllDNS()
 		assert.Nil(t, err)
@@ -83,14 +83,14 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 0, len(dns))
 	})
 	t.Run("EntryExist", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
 		CreateDNS(entry)
 		dns, err := logic.GetCustomDNS("skynet")
 		assert.Nil(t, err)
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.4", "host4", "skynet"}
+		entry := models.DNSEntry{"10.0.0.4", "", "host4", "skynet"}
 		CreateDNS(entry)
 		dns, err := logic.GetCustomDNS("skynet")
 		assert.Nil(t, err)
@@ -109,7 +109,7 @@ func TestGetDNSEntryNum(t *testing.T) {
 		assert.Equal(t, 0, num)
 	})
 	t.Run("NodeExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 		_, err := CreateDNS(entry)
 		assert.Nil(t, err)
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
@@ -128,7 +128,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Nil(t, dns)
 	})
 	t.Run("CustomDNSExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 		_, err := CreateDNS(entry)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
@@ -148,7 +148,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 		_, err := CreateDNS(entry)
 		dns, err := logic.GetDNS("skynet")
 		t.Log(dns)
@@ -165,7 +165,7 @@ func TestCreateDNS(t *testing.T) {
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 	dns, err := CreateDNS(entry)
 	assert.Nil(t, err)
 	assert.Equal(t, "newhost", dns.Name)
@@ -204,7 +204,7 @@ func TestSetDNS(t *testing.T) {
 		assert.Contains(t, string(content), "testnode.skynet")
 	})
 	t.Run("EntryExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "newhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
 		CreateDNS(entry)
 		err := logic.SetDNS()
 		assert.Nil(t, err)
@@ -224,7 +224,7 @@ func TestGetDNSEntry(t *testing.T) {
 	deleteAllNetworks()
 	createNet()
 	createTestNode()
-	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 	CreateDNS(entry)
 	t.Run("wrong net", func(t *testing.T) {
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
@@ -280,7 +280,7 @@ func TestDeleteDNS(t *testing.T) {
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
+	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
 	CreateDNS(entry)
 	t.Run("EntryExists", func(t *testing.T) {
 		err := logic.DeleteDNS("newhost", "skynet")
@@ -302,36 +302,36 @@ func TestValidateDNSUpdate(t *testing.T) {
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "myhost", "skynet"}
+	entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
 	t.Run("BadNetwork", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "myhost", "badnet"}
+		change := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
 	t.Run("EmptyNetwork", func(t *testing.T) {
 		//this can't actually happen as change.Network is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "myhost", ""}
+		change := models.DNSEntry{"10.0.0.2", "", "myhost", ""}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
-	t.Run("EmptyAddress", func(t *testing.T) {
-		//this can't actually happen as change.Address is populated if is blank
-		change := models.DNSEntry{"", "myhost", "skynet"}
-		err := logic.ValidateDNSUpdate(change, entry)
-		assert.NotNil(t, err)
-		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
-	})
+	// t.Run("EmptyAddress", func(t *testing.T) {
+	// 	//this can't actually happen as change.Address is populated if is blank
+	// 	change := models.DNSEntry{"", "", "myhost", "skynet"}
+	// 	err := logic.ValidateDNSUpdate(change, entry)
+	// 	assert.NotNil(t, err)
+	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
+	// })
 	t.Run("BadAddress", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.256.1", "myhost", "skynet"}
+		change := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	t.Run("EmptyName", func(t *testing.T) {
 		//this can't actually happen as change.Name is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "skynet"}
+		change := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -341,13 +341,13 @@ func TestValidateDNSUpdate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 		}
-		change := models.DNSEntry{"10.0.0.2", name, "skynet"}
+		change := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	t.Run("NameUnique", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "myhost", "wirecat"}
+		change := models.DNSEntry{"10.0.0.2", "", "myhost", "wirecat"}
 		CreateDNS(entry)
 		CreateDNS(change)
 		err := logic.ValidateDNSUpdate(change, entry)
@@ -363,25 +363,25 @@ func TestValidateDNSCreate(t *testing.T) {
 	database.InitializeDatabase()
 	_ = logic.DeleteDNS("mynode", "skynet")
 	t.Run("NoNetwork", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "myhost", "badnet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
 	t.Run("EmptyAddress", func(t *testing.T) {
-		entry := models.DNSEntry{"", "myhost", "skynet"}
+		entry := models.DNSEntry{"", "", "myhost", "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	})
 	t.Run("BadAddress", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.256.1", "myhost", "skynet"}
+		entry := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	t.Run("EmptyName", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -391,13 +391,13 @@ func TestValidateDNSCreate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 		}
-		entry := models.DNSEntry{"10.0.0.2", name, "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	t.Run("NameUnique", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "myhost", "skynet"}
+		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
 		_, _ = CreateDNS(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)

+ 19 - 1
controllers/ext_client.go

@@ -135,12 +135,30 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+
+	addrString := client.Address
+	if addrString != "" {
+		addrString += "/32"
+	}
+	if client.Address6 != "" {
+		if addrString != "" {
+			addrString += ","
+		}
+		addrString += client.Address6 + "/128"
+	}
+
 	keepalive := ""
 	if network.DefaultKeepalive != 0 {
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 	}
 	gwendpoint := gwnode.Endpoint + ":" + strconv.Itoa(int(gwnode.ListenPort))
 	newAllowedIPs := network.AddressRange
+	if newAllowedIPs != "" {
+		newAllowedIPs += ","
+	}
+	if network.AddressRange6 != "" {
+		newAllowedIPs += network.AddressRange6
+	}
 	if egressGatewayRanges, err := logic.GetEgressRangesOnNetwork(&client); err == nil {
 		for _, egressGatewayRange := range egressGatewayRanges {
 			newAllowedIPs += "," + egressGatewayRange
@@ -167,7 +185,7 @@ AllowedIPs = %s
 Endpoint = %s
 %s
 
-`, client.Address+"/32",
+`, addrString,
 		client.PrivateKey,
 		defaultMTU,
 		defaultDNS,

+ 6 - 5
controllers/network.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"net/http"
 	"strings"
 
@@ -152,11 +153,6 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	// if newNetwork.IsDualStack != currentNetwork.IsDualStack && newNetwork.IsDualStack == "no" {
-	// 	// Remove IPv6 address from network nodes
-	// 	RemoveNetworkNodeIPv6Addresses(currentNetwork.NetID)
-	// }
-
 	if rangeupdate {
 		err = logic.UpdateNetworkNodeAddresses(network.NetID)
 		if err != nil {
@@ -312,6 +308,11 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if network.AddressRange == "" && network.AddressRange6 == "" {
+		returnErrorResponse(w, r, formatError(fmt.Errorf("IPv4 or IPv6 CIDR required"), "badrequest"))
+		return
+	}
+
 	network, err = logic.CreateNetwork(network)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))

+ 33 - 0
controllers/network_test.go

@@ -284,6 +284,26 @@ func TestValidateNetworkUpdate(t *testing.T) {
 	}
 }
 
+func TestIpv6Network(t *testing.T) {
+	//these seem to work but not sure it the tests are really testing the functionality
+
+	database.InitializeDatabase()
+	os.Setenv("MASTER_KEY", "secretkey")
+	createNet()
+	createNetDualStack()
+	network, err := logic.GetNetwork("skynet6")
+	t.Run("Test Network Create IPv6", func(t *testing.T) {
+		assert.Nil(t, err)
+		assert.Equal(t, network.AddressRange6, "fde6:be04:fa5e:d076::/64")
+	})
+	node1 := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.50", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet6", OS: "linux"}
+	nodeErr := logic.CreateNode(&node1)
+	t.Run("Test node on network IPv6", func(t *testing.T) {
+		assert.Nil(t, nodeErr)
+		assert.Equal(t, node1.Address6, "fde6:be04:fa5e:d076::")
+	})
+}
+
 func deleteAllNetworks() {
 	deleteAllNodes()
 	nets, _ := logic.GetNetworks()
@@ -301,3 +321,16 @@ func createNet() {
 		logic.CreateNetwork(network)
 	}
 }
+
+func createNetDualStack() {
+	var network models.Network
+	network.NetID = "skynet6"
+	network.AddressRange = "10.1.2.0/24"
+	network.AddressRange6 = "fde6:be04:fa5e:d076::/64"
+	network.IsIPv4 = "yes"
+	network.IsIPv6 = "yes"
+	_, err := logic.GetNetwork("skynet6")
+	if err != nil {
+		logic.CreateNetwork(network)
+	}
+}

+ 2 - 2
controllers/relay.go

@@ -31,7 +31,7 @@ func createRelay(w http.ResponseWriter, r *http.Request) {
 	for _, relayedNode := range updatenodes {
 		err = mq.NodeUpdate(&relayedNode)
 		if err != nil {
-			logger.Log(1, "error sending update to relayed node ", relayedNode.Address, "on network", relay.NetID, ": ", err.Error())
+			logger.Log(1, "error sending update to relayed node ", relayedNode.Name, "on network", relay.NetID, ": ", err.Error())
 		}
 	}
 	w.WriteHeader(http.StatusOK)
@@ -53,7 +53,7 @@ func deleteRelay(w http.ResponseWriter, r *http.Request) {
 	for _, relayedNode := range updatenodes {
 		err = mq.NodeUpdate(&relayedNode)
 		if err != nil {
-			logger.Log(1, "error sending update to relayed node ", relayedNode.Address, "on network", netid, ": ", err.Error())
+			logger.Log(1, "error sending update to relayed node ", relayedNode.Name, "on network", netid, ": ", err.Error())
 		}
 	}
 	w.WriteHeader(http.StatusOK)

+ 3 - 3
go.mod

@@ -24,9 +24,7 @@ require (
 	golang.org/x/text v0.3.7 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
-	google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 // indirect
-	google.golang.org/grpc v1.45.0
-	google.golang.org/protobuf v1.28.0
+	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
@@ -37,6 +35,7 @@ require (
 	github.com/guumaster/hostctl v1.1.2
 	github.com/kr/pretty v0.3.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
+	github.com/seancfoley/ipaddress-go v1.1.2
 )
 
 require (
@@ -66,6 +65,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.8.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/seancfoley/bintree v1.0.1 // indirect
 	github.com/spf13/afero v1.3.2 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect

+ 4 - 65
go.sum

@@ -9,20 +9,11 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -50,12 +41,6 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
 github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -85,30 +70,15 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -122,7 +92,6 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/guumaster/cligger v0.1.1/go.mod h1:7d2cVJR7sExHITuqWUU7S9inAs+Hx1QbMDZwO+21a64=
 github.com/guumaster/hostctl v1.1.2 h1:M+DrRaLLeBt7JTh3YbE607gCgRzyxOKuqyrpBC153LQ=
 github.com/guumaster/hostctl v1.1.2/go.mod h1:n5R/s1/tUbYNN1t3J/F/70ZGUWzExgJnEAS0YZ8VWg8=
@@ -192,14 +161,12 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@@ -209,6 +176,10 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/seancfoley/bintree v1.0.1 h1:rCb5DEugf2B2DtkrxJ80CP6HT24yohDEcXPOqkQRizo=
+github.com/seancfoley/bintree v1.0.1/go.mod h1:CtE6qO6/n9H3V2CAGEC0lpaYr6/OijhNaMG/dt7P70c=
+github.com/seancfoley/ipaddress-go v1.1.2 h1:zeAUfL7foAPe1pIlT2agp17tgpwzU6YKuEAa2VrRKOw=
+github.com/seancfoley/ipaddress-go v1.1.2/go.mod h1:gR/Gr3Sx+pzusadtM9s98e/tZjvL4YnumYTPcKoHWec=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@@ -249,7 +220,6 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -263,22 +233,18 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -293,7 +259,6 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -332,9 +297,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -349,30 +312,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 h1:HPkKL4eEh/nemF/FRzYMrFsAh1ZPm5t8NqKBI/Ejlg0=
-google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
@@ -390,7 +331,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -400,4 +340,3 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 0 - 1
logic/dns.go

@@ -50,7 +50,6 @@ func SetDNS() error {
 // GetDNS - gets the DNS of a current network
 func GetDNS(network string) ([]models.DNSEntry, error) {
 
-	var dns []models.DNSEntry
 	dns, err := GetNodeDNS(network)
 	if err != nil && !database.IsEmptyRecord(err) {
 		return dns, err

+ 18 - 8
logic/extpeers.go

@@ -33,6 +33,7 @@ func GetExtPeersList(node *models.Node) ([]models.ExtPeersResponse, error) {
 			logger.Log(2, "failed to unmarshal ext client")
 			continue
 		}
+
 		if extClient.Enabled && extClient.Network == node.Network && extClient.IngressGatewayID == node.ID {
 			peers = append(peers, peer)
 		}
@@ -125,20 +126,29 @@ func CreateExtClient(extclient *models.ExtClient) error {
 		extclient.PublicKey = privateKey.PublicKey().String()
 	}
 
+	parentNetwork, err := GetNetwork(extclient.Network)
+	if err != nil {
+		return err
+	}
+
 	if extclient.Address == "" {
-		newAddress, err := UniqueAddress(extclient.Network)
-		if err != nil {
-			return err
+		if parentNetwork.IsIPv4 == "yes" {
+			newAddress, err := UniqueAddress(extclient.Network, false)
+			if err != nil {
+				return err
+			}
+			extclient.Address = newAddress
 		}
-		extclient.Address = newAddress
 	}
 
 	if extclient.Address6 == "" {
-		addr6, err := UniqueAddress6(extclient.Network)
-		if err != nil {
-			return err
+		if parentNetwork.IsIPv6 == "yes" {
+			addr6, err := UniqueAddress6(extclient.Network, false)
+			if err != nil {
+				return err
+			}
+			extclient.Address6 = addr6
 		}
-		extclient.Address6 = addr6
 	}
 
 	if extclient.ClientID == "" {

+ 62 - 0
logic/ips/ips.go

@@ -0,0 +1,62 @@
+package ips
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/seancfoley/ipaddress-go/ipaddr"
+)
+
+// GetFirstAddr - gets the first valid address in a given IPv4 CIDR
+func GetFirstAddr(cidr4 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr4).GetAddress()
+	if !currentCidr.IsIPv4() {
+		return nil, fmt.Errorf("invalid IPv4 CIDR provided to GetFirstAddr")
+	}
+	lower := currentCidr.GetLower()
+	ipParts := strings.Split(lower.GetNetIPAddr().IP.String(), ".")
+	if ipParts[len(ipParts)-1] == "0" {
+		lower = lower.Increment(1)
+	}
+	return lower, nil
+}
+
+// GetLastAddr - gets the last valid address in a given IPv4 CIDR
+func GetLastAddr(cidr4 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr4).GetAddress()
+	if !currentCidr.IsIPv4() {
+		return nil, fmt.Errorf("invalid IPv4 CIDR provided to GetLastAddr")
+	}
+	upper := currentCidr.GetUpper()
+	ipParts := strings.Split(upper.GetNetIPAddr().IP.String(), ".")
+	if ipParts[len(ipParts)-1] == "255" {
+		upper = upper.Increment(-1)
+	}
+	return upper, nil
+}
+
+// GetFirstAddr6 - gets the first valid IPv6 address in a given IPv6 CIDR
+func GetFirstAddr6(cidr6 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr6).GetAddress()
+	if !currentCidr.IsIPv6() {
+		return nil, fmt.Errorf("invalid IPv6 CIDR provided to GetFirstAddr6")
+	}
+	lower := currentCidr.GetLower()
+	ipParts := strings.Split(lower.GetNetIPAddr().IP.String(), "::")
+	if len(ipParts) == 2 {
+		if len(ipParts[len(ipParts)-1]) == 0 {
+			lower = lower.Increment(1)
+		}
+	}
+	return lower, nil
+}
+
+// GetLastAddr6 - gets the last valid IPv6 address in a given IPv6 CIDR
+func GetLastAddr6(cidr6 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr6).GetAddress()
+	if !currentCidr.IsIPv6() {
+		return nil, fmt.Errorf("invalid IPv6 CIDR provided to GetLastAddr6")
+	}
+	upper := currentCidr.GetUpper()
+	return upper, nil
+}

+ 50 - 0
logic/ips/ips_test.go

@@ -0,0 +1,50 @@
+package ips_test
+
+import (
+	"testing"
+
+	"github.com/gravitl/netmaker/logic/ips"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIp4(t *testing.T) {
+	const ipv4Cidr = "192.168.0.0/16"
+	const ipv6Cidr = "fde6:be04:fa5e:d076::/64"
+	//delete all current users
+	t.Run("Valid Ipv4", func(t *testing.T) {
+		_, err := ips.GetFirstAddr(ipv4Cidr)
+		assert.Nil(t, err)
+	})
+	t.Run("Invalid Ipv4", func(t *testing.T) {
+		_, err := ips.GetFirstAddr(ipv6Cidr)
+		assert.NotNil(t, err)
+	})
+	t.Run("Valid IPv6", func(t *testing.T) {
+		_, err := ips.GetFirstAddr6(ipv6Cidr)
+		assert.Nil(t, err)
+	})
+	t.Run("Invalid IPv6", func(t *testing.T) {
+		_, err := ips.GetFirstAddr6(ipv4Cidr)
+		assert.NotNil(t, err)
+	})
+	t.Run("Last IPv4", func(t *testing.T) {
+		addr, err := ips.GetLastAddr(ipv4Cidr)
+		assert.Nil(t, err)
+		assert.Equal(t, addr.GetNetIPAddr().IP.String(), "192.168.255.254")
+	})
+	t.Run("First IPv4", func(t *testing.T) {
+		addr, err := ips.GetFirstAddr(ipv4Cidr)
+		assert.Nil(t, err)
+		assert.Equal(t, addr.GetNetIPAddr().IP.String(), "192.168.0.1")
+	})
+	t.Run("Last IPv6", func(t *testing.T) {
+		last, err := ips.GetLastAddr6(ipv6Cidr)
+		assert.Nil(t, err)
+		assert.Equal(t, last.GetNetIPAddr().IP.String(), "fde6:be04:fa5e:d076:ffff:ffff:ffff:ffff")
+	})
+	t.Run("First IPv6", func(t *testing.T) {
+		first, err := ips.GetFirstAddr6(ipv6Cidr)
+		assert.Nil(t, err)
+		assert.Equal(t, first.GetNetIPAddr().IP.String(), "fde6:be04:fa5e:d076::")
+	})
+}

+ 51 - 81
logic/networks.go

@@ -1,7 +1,6 @@
 package logic
 
 import (
-	"encoding/binary"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -13,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
+	"github.com/gravitl/netmaker/logic/ips"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/validation"
@@ -173,71 +173,45 @@ func GetNetworkSettings(networkname string) (models.Network, error) {
 }
 
 // UniqueAddress - see if address is unique
-func UniqueAddress(networkName string) (string, error) {
+func UniqueAddress(networkName string, reverse bool) (string, error) {
 
 	var network models.Network
 	network, err := GetParentNetwork(networkName)
 	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
+		logger.Log(0, "UniqueAddressServer encountered  an error")
 		return "666", err
 	}
 
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange)
-	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
-		return "666", err
+	if network.IsIPv4 == "no" {
+		return "", fmt.Errorf("IPv4 not active on network " + networkName)
 	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
-		}
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) {
-			return ip.String(), err
-		}
-	}
-
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
-}
 
-// UniqueAddressServer - get unique address starting from last available
-func UniqueAddressServer(networkName string) (string, error) {
-
-	var network models.Network
-	network, err := GetParentNetwork(networkName)
+	newAddr, err := ips.GetFirstAddr(network.AddressRange)
 	if err != nil {
-		logger.Log(0, "UniqueAddressServer encountered  an error")
+		logger.Log(0, "UniqueAddress encountered  an error")
 		return "666", err
 	}
 
-	_, ipv4Net, err := net.ParseCIDR(network.AddressRange)
-	if err != nil {
-		logger.Log(0, "UniqueAddressServer encountered  an error")
-		return "666", err
+	incVal := 1
+	if reverse {
+		incVal = -1
+		newAddr, err = ips.GetLastAddr(network.AddressRange)
+		if err != nil {
+			if err != nil {
+				logger.Log(0, "UniqueAddressServer encountered  an error")
+				return "666", err
+			}
+		}
 	}
 
-	// convert IPNet struct mask and address to uint32
-	// network is BigEndian
-	mask := binary.BigEndian.Uint32(ipv4Net.Mask)
-	start := binary.BigEndian.Uint32(ipv4Net.IP)
-
-	// find the final address
-	finish := (start & mask) | (mask ^ 0xffffffff)
-
-	// loop through addresses as uint32
-	for i := finish - 1; i > start; i-- {
-		// convert back to net.IP
-		ip := make(net.IP, 4)
-		binary.BigEndian.PutUint32(ip, i)
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) {
-			return ip.String(), err
+	for ; newAddr.ToAddressString().IsValid(); newAddr = newAddr.Increment(int64(incVal)) {
+		if IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, false) &&
+			IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, false) {
+			return newAddr.GetNetIPAddr().IP.String(), nil
 		}
 	}
 
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", fmt.Errorf("no unique server addresses found")
+	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", errors.New("ERROR: No unique addresses available. Check network subnet")
 }
 
 // IsIPUnique - checks if an IP is unique
@@ -270,7 +244,7 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 }
 
 // UniqueAddress6 - see if ipv6 address is unique
-func UniqueAddress6(networkName string) (string, error) {
+func UniqueAddress6(networkName string, reverse bool) (string, error) {
 
 	var network models.Network
 	network, err := GetParentNetwork(networkName)
@@ -278,28 +252,35 @@ func UniqueAddress6(networkName string) (string, error) {
 		fmt.Println("Network Not Found")
 		return "", err
 	}
-	if network.IsDualStack == "no" {
-		return "", nil
+	if network.IsIPv6 == "no" {
+		return "", fmt.Errorf("IPv6 not active on network " + networkName)
 	}
 
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange6)
+	newAddr6, err := ips.GetFirstAddr6(network.AddressRange6)
 	if err != nil {
-		fmt.Println("UniqueAddress6 encountered  an error")
 		return "666", err
 	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
+
+	incVal := 1
+	if reverse {
+		incVal = -1
+		newAddr6, err = ips.GetLastAddr6(network.AddressRange6)
+		if err != nil {
+			if err != nil {
+				logger.Log(0, "UniqueAddress6Server encountered  an error")
+				return "666", err
+			}
 		}
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, true) {
-			return ip.String(), err
+	}
+
+	for ; newAddr6.ToAddressString().IsValid(); newAddr6 = newAddr6.Increment(int64(incVal)) {
+		if IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, true) &&
+			IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, true) {
+			return newAddr6.GetNetIPAddr().IP.String(), nil
 		}
 	}
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
+
+	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", errors.New("ERROR: No unique IPv6 addresses available. Check network subnet")
 }
 
 // GetLocalIP - gets the local ip
@@ -380,9 +361,9 @@ func UpdateNetworkLocalAddresses(networkName string) error {
 			var ipaddr string
 			var iperr error
 			if node.IsServer == "yes" {
-				ipaddr, iperr = UniqueAddressServer(networkName)
+				ipaddr, iperr = UniqueAddress(networkName, true)
 			} else {
-				ipaddr, iperr = UniqueAddress(networkName)
+				ipaddr, iperr = UniqueAddress(networkName, false)
 			}
 			if iperr != nil {
 				fmt.Println("error in node  address assignment!")
@@ -441,7 +422,6 @@ func RemoveNetworkNodeIPv6Addresses(networkName string) error {
 			return err
 		}
 		if node.Network == networkName {
-			node.IsDualStack = "no"
 			node.Address6 = ""
 			data, err := json.Marshal(&node)
 			if err != nil {
@@ -474,9 +454,9 @@ func UpdateNetworkNodeAddresses(networkName string) error {
 			var ipaddr string
 			var iperr error
 			if node.IsServer == "yes" {
-				ipaddr, iperr = UniqueAddressServer(networkName)
+				ipaddr, iperr = UniqueAddress(networkName, true)
 			} else {
-				ipaddr, iperr = UniqueAddress(networkName)
+				ipaddr, iperr = UniqueAddress(networkName, false)
 			}
 			if iperr != nil {
 				fmt.Println("error in node  address assignment!")
@@ -537,16 +517,6 @@ func UpdateNetwork(currentNetwork *models.Network, newNetwork *models.Network) (
 	return false, false, false, errors.New("failed to update network " + newNetwork.NetID + ", cannot change netid.")
 }
 
-// Inc - increments an IP
-func Inc(ip net.IP) {
-	for j := len(ip) - 1; j >= 0; j-- {
-		ip[j]++
-		if ip[j] > 0 {
-			break
-		}
-	}
-}
-
 // GetNetwork - gets a network from database
 func GetNetwork(networkname string) (models.Network, error) {
 
@@ -561,7 +531,7 @@ func GetNetwork(networkname string) (models.Network, error) {
 	return network, nil
 }
 
-// Network.NetIDInNetworkCharSet - checks if a netid of a network uses valid characters
+// NetIDInNetworkCharSet - checks if a netid of a network uses valid characters
 func NetIDInNetworkCharSet(network *models.Network) bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
@@ -574,7 +544,7 @@ func NetIDInNetworkCharSet(network *models.Network) bool {
 	return true
 }
 
-// Network.Validate - validates fields of an network struct
+// Validate - validates fields of an network struct
 func ValidateNetwork(network *models.Network, isUpdate bool) error {
 	v := validator.New()
 	_ = v.RegisterValidation("netid_valid", func(fl validator.FieldLevel) bool {
@@ -637,7 +607,7 @@ func KeyUpdate(netname string) (models.Network, error) {
 	return models.Network{}, nil
 }
 
-//SaveNetwork - save network struct to database
+// SaveNetwork - save network struct to database
 func SaveNetwork(network *models.Network) error {
 	data, err := json.Marshal(network)
 	if err != nil {

+ 18 - 20
logic/nodes.go

@@ -270,21 +270,30 @@ func CreateNode(node *models.Node) error {
 
 	SetNodeDefaults(node)
 
-	if node.IsServer == "yes" {
-		if node.Address, err = UniqueAddressServer(node.Network); err != nil {
-			return err
+	defaultACLVal := acls.Allowed
+	parentNetwork, err := GetNetwork(node.Network)
+	if err == nil {
+		if parentNetwork.DefaultACL != "yes" {
+			defaultACLVal = acls.NotAllowed
 		}
-	} else if node.Address == "" {
-		if node.Address, err = UniqueAddress(node.Network); err != nil {
-			return err
+	}
+
+	reverse := node.IsServer == "yes"
+	if node.Address == "" {
+		if parentNetwork.IsIPv4 == "yes" {
+			if node.Address, err = UniqueAddress(node.Network, reverse); err != nil {
+				return err
+			}
 		}
 	} else if !IsIPUnique(node.Network, node.Address, database.NODES_TABLE_NAME, false) {
 		return fmt.Errorf("invalid address: ipv4 " + node.Address + " is not unique")
 	}
 
 	if node.Address6 == "" {
-		if node.Address6, err = UniqueAddress6(node.Network); err != nil {
-			return err
+		if parentNetwork.IsIPv6 == "yes" {
+			if node.Address6, err = UniqueAddress6(node.Network, reverse); err != nil {
+				return err
+			}
 		}
 	} else if !IsIPUnique(node.Network, node.Address6, database.NODES_TABLE_NAME, true) {
 		return fmt.Errorf("invalid address: ipv6 " + node.Address6 + " is not unique")
@@ -312,14 +321,6 @@ func CreateNode(node *models.Node) error {
 		return err
 	}
 
-	defaultACLVal := acls.Allowed
-	parentNetwork, err := GetNetwork(node.Network)
-	if err == nil {
-		if parentNetwork.DefaultACL != "yes" {
-			defaultACLVal = acls.NotAllowed
-		}
-	}
-
 	_, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID), defaultACLVal)
 	if err != nil {
 		logger.Log(1, "failed to create node ACL for node,", node.ID, "err:", err.Error())
@@ -428,9 +429,7 @@ func SetNodeDefaults(node *models.Node) {
 		}
 	}
 	// == Parent Network settings ==
-	if node.IsDualStack == "" {
-		node.IsDualStack = parentNetwork.IsDualStack
-	}
+
 	if node.MTU == 0 {
 		node.MTU = parentNetwork.DefaultMTU
 	}
@@ -438,7 +437,6 @@ func SetNodeDefaults(node *models.Node) {
 	node.SetIPForwardingDefault()
 	node.SetDNSOnDefault()
 	node.SetIsLocalDefault()
-	node.SetIsDualStackDefault()
 	node.SetLastModified()
 	node.SetDefaultName()
 	node.SetLastCheckIn()

+ 57 - 41
logic/peers.go

@@ -13,6 +13,7 @@ import (
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
+	"github.com/seancfoley/ipaddress-go/ipaddr"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
@@ -34,19 +35,19 @@ func GetHubPeer(networkName string) []models.Node {
 */
 
 // GetNodePeers - fetches peers for a given node
-func GetNodePeers(networkName, nodeid string, excludeRelayed bool, isP2S bool) ([]models.Node, error) {
+func GetNodePeers(network *models.Network, nodeid string, excludeRelayed bool, isP2S bool) ([]models.Node, error) {
 	var peers []models.Node
-	var networkNodes, egressNetworkNodes, err = getNetworkEgressAndNodes(networkName)
+	var networkNodes, egressNetworkNodes, err = getNetworkEgressAndNodes(network.NetID)
 	if err != nil {
 		return peers, nil
 	}
 
-	udppeers, errN := database.GetPeers(networkName)
+	udppeers, errN := database.GetPeers(network.NetID)
 	if errN != nil {
 		logger.Log(2, errN.Error())
 	}
 
-	currentNetworkACLs, aclErr := nodeacls.FetchAllACLs(nodeacls.NetworkID(networkName))
+	currentNetworkACLs, aclErr := nodeacls.FetchAllACLs(nodeacls.NetworkID(network.NetID))
 	if aclErr != nil {
 		return peers, aclErr
 	}
@@ -63,10 +64,9 @@ func GetNodePeers(networkName, nodeid string, excludeRelayed bool, isP2S bool) (
 		}
 
 		peer.IsIngressGateway = node.IsIngressGateway
-		isDualStack := node.IsDualStack == "yes"
 		allow := node.IsRelayed != "yes" || !excludeRelayed
 
-		if node.Network == networkName && node.IsPending != "yes" && allow {
+		if node.Network == network.NetID && node.IsPending != "yes" && allow {
 			peer = setPeerInfo(&node)
 			if node.UDPHolePunch == "yes" && errN == nil && CheckEndpoint(udppeers[node.PublicKey]) {
 				endpointstring := udppeers[node.PublicKey]
@@ -84,12 +84,7 @@ func GetNodePeers(networkName, nodeid string, excludeRelayed bool, isP2S bool) (
 				peer.ListenPort = node.LocalListenPort
 			}
 			if node.IsRelay == "yes" {
-				network, err := GetNetwork(networkName)
-				if err == nil {
-					peer.AllowedIPs = append(peer.AllowedIPs, network.AddressRange)
-				} else {
-					peer.AllowedIPs = append(peer.AllowedIPs, node.RelayAddrs...)
-				}
+				peer.AllowedIPs = append(peer.AllowedIPs, network.AddressRange)
 				for _, egressNode := range egressNetworkNodes {
 					if egressNode.IsRelayed == "yes" && StringSliceContains(node.RelayAddrs, egressNode.Address) {
 						peer.AllowedIPs = append(peer.AllowedIPs, egressNode.EgressGatewayRanges...)
@@ -99,8 +94,10 @@ func GetNodePeers(networkName, nodeid string, excludeRelayed bool, isP2S bool) (
 			if peer.IsIngressGateway == "yes" { // handle ingress stuff
 				if currentExtClients, err := GetExtPeersList(&node); err == nil {
 					for i := range currentExtClients {
-						peer.AllowedIPs = append(peer.AllowedIPs, currentExtClients[i].Address)
-						if isDualStack {
+						if network.IsIPv4 == "yes" && currentExtClients[i].Address != "" {
+							peer.AllowedIPs = append(peer.AllowedIPs, currentExtClients[i].Address)
+						}
+						if network.IsIPv6 == "yes" && currentExtClients[i].Address6 != "" {
 							peer.AllowedIPs = append(peer.AllowedIPs, currentExtClients[i].Address6)
 						}
 					}
@@ -135,7 +132,7 @@ func GetPeersList(refnode *models.Node) ([]models.Node, error) {
 		isP2S = true
 	}
 	if relayedNodeAddr == "" {
-		peers, err = GetNodePeers(networkName, refnode.ID, excludeRelayed, isP2S)
+		peers, err = GetNodePeers(&network, refnode.ID, excludeRelayed, isP2S)
 	} else {
 		var relayNode models.Node
 		relayNode, err = GetNodeRelay(networkName, relayedNodeAddr)
@@ -155,7 +152,7 @@ func GetPeersList(refnode *models.Node) ([]models.Node, error) {
 			} else {
 				peerNode.AllowedIPs = append(peerNode.AllowedIPs, peerNode.RelayAddrs...)
 			}
-			nodepeers, err := GetNodePeers(networkName, refnode.ID, false, isP2S)
+			nodepeers, err := GetNodePeers(&network, refnode.ID, false, isP2S)
 			if err == nil && peerNode.UDPHolePunch == "yes" {
 				for _, nodepeer := range nodepeers {
 					if nodepeer.Address == peerNode.Address {
@@ -285,20 +282,26 @@ func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, error) {
 			continue
 		}
 
+		var allowedips []net.IPNet
 		var peer wgtypes.PeerConfig
-		var peeraddr = net.IPNet{
-			IP:   net.ParseIP(extPeer.Address),
-			Mask: net.CIDRMask(32, 32),
+		if extPeer.Address != "" {
+			var peeraddr = net.IPNet{
+				IP:   net.ParseIP(extPeer.Address),
+				Mask: net.CIDRMask(32, 32),
+			}
+			if peeraddr.IP != nil && peeraddr.Mask != nil {
+				allowedips = append(allowedips, peeraddr)
+			}
 		}
-		var allowedips []net.IPNet
-		allowedips = append(allowedips, peeraddr)
 
 		if extPeer.Address6 != "" {
 			var addr6 = net.IPNet{
 				IP:   net.ParseIP(extPeer.Address6),
 				Mask: net.CIDRMask(128, 128),
 			}
-			allowedips = append(allowedips, addr6)
+			if addr6.IP != nil && addr6.Mask != nil {
+				allowedips = append(allowedips, addr6)
+			}
 		}
 		peer = wgtypes.PeerConfig{
 			PublicKey:         pubkey,
@@ -314,23 +317,43 @@ func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, error) {
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
 func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 	var allowedips []net.IPNet
-	var peeraddr = net.IPNet{
-		IP:   net.ParseIP(peer.Address),
-		Mask: net.CIDRMask(32, 32),
+
+	if peer.Address != "" {
+		var peeraddr = net.IPNet{
+			IP:   net.ParseIP(peer.Address),
+			Mask: net.CIDRMask(32, 32),
+		}
+		allowedips = append(allowedips, peeraddr)
+	}
+
+	if peer.Address6 != "" {
+		var addr6 = net.IPNet{
+			IP:   net.ParseIP(peer.Address6),
+			Mask: net.CIDRMask(128, 128),
+		}
+		allowedips = append(allowedips, addr6)
 	}
-	dualstack := false
-	allowedips = append(allowedips, peeraddr)
+
 	// handle manually set peers
 	for _, allowedIp := range peer.AllowedIPs {
-		if _, ipnet, err := net.ParseCIDR(allowedIp); err == nil {
-			nodeEndpointArr := strings.Split(node.Endpoint, ":")
-			if !ipnet.Contains(net.IP(nodeEndpointArr[0])) && ipnet.IP.String() != peer.Address { // don't need to add an allowed ip that already exists..
-				allowedips = append(allowedips, *ipnet)
+		currentAddr := ipaddr.NewIPAddressString(allowedIp).GetAddress()
+		if currentAddr.IsIPv4() {
+			if _, ipnet, err := net.ParseCIDR(allowedIp); err == nil {
+				nodeEndpointArr := strings.Split(node.Endpoint, ":")
+				if !ipnet.Contains(net.IP(nodeEndpointArr[0])) && ipnet.IP.String() != peer.Address { // don't need to add an allowed ip that already exists..
+					allowedips = append(allowedips, *ipnet)
+				}
+			} else if appendip := net.ParseIP(allowedIp); appendip != nil && allowedIp != peer.Address {
+				ipnet := net.IPNet{
+					IP:   net.ParseIP(allowedIp),
+					Mask: net.CIDRMask(32, 32),
+				}
+				allowedips = append(allowedips, ipnet)
 			}
-		} else if appendip := net.ParseIP(allowedIp); appendip != nil && allowedIp != peer.Address {
+		} else if currentAddr.IsIPv6() {
 			ipnet := net.IPNet{
 				IP:   net.ParseIP(allowedIp),
-				Mask: net.CIDRMask(32, 32),
+				Mask: net.CIDRMask(128, 128),
 			}
 			allowedips = append(allowedips, ipnet)
 		}
@@ -356,18 +379,11 @@ func GetAllowedIPs(node, peer *models.Node) []net.IPNet {
 				continue // skip adding egress range if overlaps with node's local ip
 			}
 			if err != nil {
-				log.Println("ERROR ENCOUNTERED SETTING GATEWAY")
+				logger.Log(1, "error encountered when setting egress range", err.Error())
 			} else {
 				allowedips = append(allowedips, *ipnet)
 			}
 		}
 	}
-	if peer.Address6 != "" && dualstack {
-		var addr6 = net.IPNet{
-			IP:   net.ParseIP(peer.Address6),
-			Mask: net.CIDRMask(128, 128),
-		}
-		allowedips = append(allowedips, addr6)
-	}
 	return allowedips
 }

+ 51 - 29
logic/server.go

@@ -16,6 +16,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
+	"github.com/seancfoley/ipaddress-go/ipaddr"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
@@ -237,24 +238,47 @@ func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []stri
 		}
 
 		var peer wgtypes.PeerConfig
-		var peeraddr = net.IPNet{
-			IP:   net.ParseIP(node.Address),
-			Mask: net.CIDRMask(32, 32),
+		var allowedips = []net.IPNet{}
+		if node.Address != "" {
+			var peeraddr = net.IPNet{
+				IP:   net.ParseIP(node.Address),
+				Mask: net.CIDRMask(32, 32),
+			}
+			if peeraddr.IP != nil && peeraddr.Mask != nil {
+				allowedips = append(allowedips, peeraddr)
+			}
 		}
-		var allowedips = []net.IPNet{
-			peeraddr,
+
+		if node.Address6 != "" {
+			var addr6 = net.IPNet{
+				IP:   net.ParseIP(node.Address6),
+				Mask: net.CIDRMask(128, 128),
+			}
+			if addr6.IP != nil && addr6.Mask != nil {
+				allowedips = append(allowedips, addr6)
+			}
 		}
+
 		// handle manually set peers
 		for _, allowedIp := range node.AllowedIPs {
-			if _, ipnet, err := net.ParseCIDR(allowedIp); err == nil {
-				nodeEndpointArr := strings.Split(node.Endpoint, ":")
-				if !ipnet.Contains(net.IP(nodeEndpointArr[0])) && ipnet.IP.String() != node.Address { // don't need to add an allowed ip that already exists..
-					allowedips = append(allowedips, *ipnet)
+			currentIP := ipaddr.NewIPAddressString(allowedIp).GetAddress()
+			if currentIP.IsIPv4() {
+				if _, ipnet, err := net.ParseCIDR(allowedIp); err == nil {
+					nodeEndpointArr := strings.Split(node.Endpoint, ":")
+					if !ipnet.Contains(net.IP(nodeEndpointArr[0])) && ipnet.IP.String() != node.Address { // don't need to add an allowed ip that already exists..
+						allowedips = append(allowedips, *ipnet)
+					}
+				} else if appendip := net.ParseIP(allowedIp); appendip != nil && allowedIp != node.Address {
+					ipnet := net.IPNet{
+						IP:   net.ParseIP(allowedIp),
+						Mask: net.CIDRMask(32, 32),
+					}
+					allowedips = append(allowedips, ipnet)
 				}
-			} else if appendip := net.ParseIP(allowedIp); appendip != nil && allowedIp != node.Address {
+			} else if currentIP.IsIPv6() {
 				ipnet := net.IPNet{
-					IP:   net.ParseIP(allowedIp),
-					Mask: net.CIDRMask(32, 32),
+					IP:   currentIP.GetNetIP(),
+					Mask: net.CIDRMask(128, 128),
 				}
 				allowedips = append(allowedips, ipnet)
 			}
@@ -285,15 +309,8 @@ func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []stri
 					allowedips = append(allowedips, *ipnet)
 				}
 			}
-			ranges = nil
-		}
-		if node.Address6 != "" && serverNode.IsDualStack == "yes" {
-			var addr6 = net.IPNet{
-				IP:   net.ParseIP(node.Address6),
-				Mask: net.CIDRMask(128, 128),
-			}
-			allowedips = append(allowedips, addr6)
 		}
+
 		peer = wgtypes.PeerConfig{
 			PublicKey:                   pubkey,
 			PersistentKeepaliveInterval: &(keepalivedur),
@@ -347,22 +364,27 @@ func GetServerExtPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, error) {
 		if serverNode.PublicKey == extPeer.PublicKey {
 			continue
 		}
+		var allowedips = []net.IPNet{}
 
 		var peer wgtypes.PeerConfig
-		var peeraddr = net.IPNet{
-			IP:   net.ParseIP(extPeer.Address),
-			Mask: net.CIDRMask(32, 32),
-		}
-		var allowedips = []net.IPNet{
-			peeraddr,
+		if extPeer.Address != "" {
+			newAddr := net.IPNet{
+				IP:   net.ParseIP(extPeer.Address),
+				Mask: net.CIDRMask(32, 32),
+			}
+			if &newAddr != nil {
+				allowedips = append(allowedips, newAddr)
+			}
 		}
 
-		if extPeer.Address6 != "" && serverNode.IsDualStack == "yes" {
-			var addr6 = net.IPNet{
+		if extPeer.Address6 != "" {
+			newAddr6 := net.IPNet{
 				IP:   net.ParseIP(extPeer.Address6),
 				Mask: net.CIDRMask(128, 128),
 			}
-			allowedips = append(allowedips, addr6)
+			if &newAddr6 != nil {
+				allowedips = append(allowedips, newAddr6)
+			}
 		}
 		peer = wgtypes.PeerConfig{
 			PublicKey:         pubkey,

+ 2 - 8
logic/wireguard.go

@@ -50,9 +50,9 @@ func HasPeerConnected(node *models.Node) bool {
 func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 	// single comparison statements
 	if newNode.Endpoint != currentNode.Endpoint ||
-		newNode.LocalAddress != currentNode.LocalAddress ||
 		newNode.PublicKey != currentNode.PublicKey ||
 		newNode.Address != currentNode.Address ||
+		newNode.Address6 != currentNode.Address6 ||
 		newNode.IsEgressGateway != currentNode.IsEgressGateway ||
 		newNode.IsIngressGateway != currentNode.IsIngressGateway ||
 		newNode.IsRelay != currentNode.IsRelay ||
@@ -67,12 +67,6 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 	}
 
 	// multi-comparison statements
-	if newNode.IsDualStack == "yes" {
-		if newNode.Address6 != currentNode.Address6 {
-			return true
-		}
-	}
-
 	if newNode.IsEgressGateway == "yes" {
 		if len(currentNode.EgressGatewayRanges) != len(newNode.EgressGatewayRanges) {
 			return true
@@ -239,7 +233,7 @@ func initWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 				_, _ = ncutils.RunCmd(ipExec+" -4 route add "+gateway+" dev "+ifacename, true)
 			}
 		}
-		if node.Address6 != "" && node.IsDualStack == "yes" {
+		if node.Address6 != "" {
 			logger.Log(1, "adding address:", node.Address6)
 			_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/64", true)
 		}

+ 5 - 3
models/dnsEntry.go

@@ -1,8 +1,10 @@
 //TODO:  Either add a returnNetwork and returnKey, or delete this
 package models
 
+// DNSEntry - a DNS entry represented as struct
 type DNSEntry struct {
-	Address string `json:"address" bson:"address" validate:"required,ip"`
-	Name    string `json:"name" bson:"name" validate:"required,name_unique,min=1,max=192"`
-	Network string `json:"network" bson:"network" validate:"network_exists"`
+	Address  string `json:"address" bson:"address" validate:"ip"`
+	Address6 string `json:"address6" bson:"address6"`
+	Name     string `json:"name" bson:"name" validate:"required,name_unique,min=1,max=192"`
+	Network  string `json:"network" bson:"network" validate:"network_exists"`
 }

+ 7 - 10
models/network.go

@@ -7,8 +7,8 @@ import (
 // Network Struct - contains info for a given unique network
 //At  some point, need to replace all instances of Name with something else like  Identifier
 type Network struct {
-	AddressRange        string      `json:"addressrange" bson:"addressrange" validate:"required,cidr"`
-	AddressRange6       string      `json:"addressrange6" bson:"addressrange6" validate:"regexp=^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"`
+	AddressRange        string      `json:"addressrange" bson:"addressrange" validate:"omitempty,cidr"`
+	AddressRange6       string      `json:"addressrange6" bson:"addressrange6"`
 	NetID               string      `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
 	NodesLastModified   int64       `json:"nodeslastmodified" bson:"nodeslastmodified"`
 	NetworkLastModified int64       `json:"networklastmodified" bson:"networklastmodified"`
@@ -21,7 +21,6 @@ type Network struct {
 	AccessKeys          []AccessKey `json:"accesskeys" bson:"accesskeys"`
 	AllowManualSignUp   string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
 	IsLocal             string      `json:"islocal" bson:"islocal" validate:"checkyesorno"`
-	IsDualStack         string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"`
 	IsIPv4              string      `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
 	IsIPv6              string      `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
 	IsPointToSite       string      `json:"ispointtosite" bson:"ispointtosite" validate:"checkyesorno"`
@@ -77,15 +76,13 @@ func (network *Network) SetDefaults() {
 	if network.AllowManualSignUp == "" {
 		network.AllowManualSignUp = "no"
 	}
-	if network.IsDualStack == "" {
-		network.IsDualStack = "no"
-	}
-	if network.IsDualStack == "yes" {
-		network.IsIPv6 = "yes"
+
+	if network.IsIPv4 == "" {
 		network.IsIPv4 = "yes"
-	} else {
+	}
+
+	if network.IsIPv6 == "" {
 		network.IsIPv6 = "no"
-		network.IsIPv4 = "yes"
 	}
 
 	if network.DefaultMTU == 0 {

+ 55 - 62
models/node.go

@@ -33,55 +33,53 @@ var seededRand *rand.Rand = rand.New(
 
 // Node - struct for node model
 type Node struct {
-	ID                  string   `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5"`
-	Address             string   `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
-	Address6            string   `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
-	LocalAddress        string   `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty,ip"`
-	LocalListenPort     int32    `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
-	Name                string   `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
-	NetworkSettings     Network  `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
-	ListenPort          int32    `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
-	PublicKey           string   `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
-	Endpoint            string   `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
-	PostUp              string   `json:"postup" bson:"postup" yaml:"postup"`
-	PostDown            string   `json:"postdown" bson:"postdown" yaml:"postdown"`
-	AllowedIPs          []string `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
-	PersistentKeepalive int32    `json:"persistentkeepalive" bson:"persistentkeepalive" yaml:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
-	IsHub               string   `json:"ishub" bson:"ishub" yaml:"ishub" validate:"checkyesorno"`
-	AccessKey           string   `json:"accesskey" bson:"accesskey" yaml:"accesskey"`
-	Interface           string   `json:"interface" bson:"interface" yaml:"interface"`
-	LastModified        int64    `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
-	ExpirationDateTime  int64    `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
-	LastPeerUpdate      int64    `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
-	LastCheckIn         int64    `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
-	MacAddress          string   `json:"macaddress" bson:"macaddress" yaml:"macaddress" validate:"macaddress_unique"`
-	Password            string   `json:"password" bson:"password" yaml:"password" validate:"required,min=6"`
-	Network             string   `json:"network" bson:"network" yaml:"network" validate:"network_exists"`
-	IsRelayed           string   `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	IsPending           string   `json:"ispending" bson:"ispending" yaml:"ispending"`
-	IsRelay             string   `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
-	IsDocker            string   `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
-	IsK8S               string   `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
-	IsEgressGateway     string   `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway"`
-	IsIngressGateway    string   `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway"`
-	EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
-	RelayAddrs          []string `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
-	IngressGatewayRange string   `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
-	IsStatic            string   `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
-	UDPHolePunch        string   `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
-	//PullChanges         string      `json:"pullchanges" bson:"pullchanges" yaml:"pullchanges" validate:"checkyesorno"`
-	DNSOn        string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
-	IsDualStack  string      `json:"isdualstack" bson:"isdualstack" yaml:"isdualstack" validate:"checkyesorno"`
-	IsServer     string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
-	Action       string      `json:"action" bson:"action" yaml:"action"`
-	IsLocal      string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
-	LocalRange   string      `json:"localrange" bson:"localrange" yaml:"localrange"`
-	IPForwarding string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
-	OS           string      `json:"os" bson:"os" yaml:"os"`
-	MTU          int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
-	Version      string      `json:"version" bson:"version" yaml:"version"`
-	Server       string      `json:"server" bson:"server" yaml:"server"`
-	TrafficKeys  TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
+	ID                  string      `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5"`
+	Address             string      `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"`
+	Address6            string      `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"`
+	LocalAddress        string      `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty,ip"`
+	Name                string      `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"`
+	NetworkSettings     Network     `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"`
+	ListenPort          int32       `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
+	LocalListenPort     int32       `json:"locallistenport" bson:"locallistenport" yaml:"locallistenport" validate:"numeric,min=0,max=65535"`
+	PublicKey           string      `json:"publickey" bson:"publickey" yaml:"publickey" validate:"required,base64"`
+	Endpoint            string      `json:"endpoint" bson:"endpoint" yaml:"endpoint" validate:"required,ip"`
+	PostUp              string      `json:"postup" bson:"postup" yaml:"postup"`
+	PostDown            string      `json:"postdown" bson:"postdown" yaml:"postdown"`
+	AllowedIPs          []string    `json:"allowedips" bson:"allowedips" yaml:"allowedips"`
+	PersistentKeepalive int32       `json:"persistentkeepalive" bson:"persistentkeepalive" yaml:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
+	IsHub               string      `json:"ishub" bson:"ishub" yaml:"ishub" validate:"checkyesorno"`
+	AccessKey           string      `json:"accesskey" bson:"accesskey" yaml:"accesskey"`
+	Interface           string      `json:"interface" bson:"interface" yaml:"interface"`
+	LastModified        int64       `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
+	ExpirationDateTime  int64       `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
+	LastPeerUpdate      int64       `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
+	LastCheckIn         int64       `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
+	MacAddress          string      `json:"macaddress" bson:"macaddress" yaml:"macaddress" validate:"macaddress_unique"`
+	Password            string      `json:"password" bson:"password" yaml:"password" validate:"required,min=6"`
+	Network             string      `json:"network" bson:"network" yaml:"network" validate:"network_exists"`
+	IsRelayed           string      `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	IsPending           string      `json:"ispending" bson:"ispending" yaml:"ispending"`
+	IsRelay             string      `json:"isrelay" bson:"isrelay" yaml:"isrelay" validate:"checkyesorno"`
+	IsDocker            string      `json:"isdocker" bson:"isdocker" yaml:"isdocker" validate:"checkyesorno"`
+	IsK8S               string      `json:"isk8s" bson:"isk8s" yaml:"isk8s" validate:"checkyesorno"`
+	IsEgressGateway     string      `json:"isegressgateway" bson:"isegressgateway" yaml:"isegressgateway"`
+	IsIngressGateway    string      `json:"isingressgateway" bson:"isingressgateway" yaml:"isingressgateway"`
+	EgressGatewayRanges []string    `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
+	RelayAddrs          []string    `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
+	IngressGatewayRange string      `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
+	IsStatic            string      `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
+	UDPHolePunch        string      `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
+	DNSOn               string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
+	IsServer            string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
+	Action              string      `json:"action" bson:"action" yaml:"action"`
+	IsLocal             string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
+	LocalRange          string      `json:"localrange" bson:"localrange" yaml:"localrange"`
+	IPForwarding        string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
+	OS                  string      `json:"os" bson:"os" yaml:"os"`
+	MTU                 int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
+	Version             string      `json:"version" bson:"version" yaml:"version"`
+	Server              string      `json:"server" bson:"server" yaml:"server"`
+	TrafficKeys         TrafficKeys `json:"traffickeys" bson:"traffickeys" yaml:"traffickeys"`
 }
 
 // NodesArray - used for node sorting
@@ -102,6 +100,14 @@ func isLess(ipA string, ipB string) bool {
 	return bytes.Compare(ipNetA, ipNetB) < 0
 }
 
+// Node.PrimaryAddress - return ipv4 address if present, else return ipv6
+func (node *Node) PrimaryAddress() string {
+	if node.Address != "" {
+		return node.Address
+	}
+	return node.Address6
+}
+
 // Node.SetDefaultMTU - sets default MTU of a node
 func (node *Node) SetDefaultMTU() {
 	if node.MTU == 0 {
@@ -200,13 +206,6 @@ func (node *Node) SetDNSOnDefault() {
 	}
 }
 
-// Node.SetIsDualStackDefault - set is dual stack default status
-func (node *Node) SetIsDualStackDefault() {
-	if node.IsDualStack == "" {
-		node.IsDualStack = "no"
-	}
-}
-
 // Node.SetIsServerDefault - sets node isserver default
 func (node *Node) SetIsServerDefault() {
 	if node.IsServer != "yes" {
@@ -349,18 +348,12 @@ func (newNode *Node) Fill(currentNode *Node) {
 	if newNode.DNSOn == "" {
 		newNode.DNSOn = currentNode.DNSOn
 	}
-	if newNode.IsDualStack == "" {
-		newNode.IsDualStack = currentNode.IsDualStack
-	}
 	if newNode.IsLocal == "" {
 		newNode.IsLocal = currentNode.IsLocal
 	}
 	if newNode.IPForwarding == "" {
 		newNode.IPForwarding = currentNode.IPForwarding
 	}
-	//if newNode.Roaming == "" {
-	//newNode.Roaming = currentNode.Roaming
-	//}
 	if newNode.Action == "" {
 		newNode.Action = currentNode.Action
 	}

+ 0 - 6
netclient/cli_options/flags.go

@@ -144,12 +144,6 @@ func GetFlags(hostname string) []cli.Flag {
 			Value:   "",
 			Usage:   "Sets endpoint to local address if 'yes'. Ignores if 'no'. Will retrieve from network if unset.",
 		},
-		&cli.StringFlag{
-			Name:    "isdualstack",
-			EnvVars: []string{"NETCLIENT_IS_DUALSTACK"},
-			Value:   "",
-			Usage:   "Sets ipv6 address if 'yes'. Ignores if 'no'. Will retrieve from network if unset.",
-		},
 		&cli.StringFlag{
 			Name:    "udpholepunch",
 			EnvVars: []string{"NETCLIENT_UDP_HOLEPUNCH"},

+ 1 - 2
netclient/config/config.go

@@ -232,12 +232,11 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 	cfg.Node.MacAddress = c.String("macaddress")
 	cfg.Node.LocalAddress = c.String("localaddress")
 	cfg.Node.Address = c.String("address")
-	cfg.Node.Address6 = c.String("addressIPV6")
+	cfg.Node.Address6 = c.String("address6")
 	//cfg.Node.Roaming = c.String("roaming")
 	cfg.Node.DNSOn = c.String("dnson")
 	cfg.Node.IsLocal = c.String("islocal")
 	cfg.Node.IsStatic = c.String("isstatic")
-	cfg.Node.IsDualStack = c.String("isdualstack")
 	cfg.Node.PostUp = c.String("postup")
 	cfg.Node.PostDown = c.String("postdown")
 	cfg.Node.ListenPort = int32(c.Int("port"))

+ 4 - 3
netclient/functions/common.go

@@ -177,9 +177,10 @@ func LeaveNetwork(network string, force bool) error {
 	wgClient, wgErr := wgctrl.New()
 	if wgErr == nil {
 		removeIface := cfg.Node.Interface
+		queryAddr := cfg.Node.PrimaryAddress()
 		if ncutils.IsMac() {
 			var macIface string
-			macIface, wgErr = local.GetMacIface(cfg.Node.Address)
+			macIface, wgErr = local.GetMacIface(queryAddr)
 			if wgErr == nil && removeIface != "" {
 				removeIface = macIface
 			}
@@ -187,10 +188,10 @@ func LeaveNetwork(network string, force bool) error {
 		}
 		dev, devErr := wgClient.Device(removeIface)
 		if devErr == nil {
-			local.FlushPeerRoutes(removeIface, cfg.Node.Address, dev.Peers[:])
+			local.FlushPeerRoutes(removeIface, queryAddr, dev.Peers[:])
 			_, cidr, cidrErr := net.ParseCIDR(cfg.NetworkSettings.AddressRange)
 			if cidrErr == nil {
-				local.RemoveCIDRRoute(removeIface, cfg.Node.Address, cidr)
+				local.RemoveCIDRRoute(removeIface, queryAddr, cidr)
 			}
 		} else {
 			logger.Log(1, "could not flush peer routes when leaving network, ", cfg.Node.Network)

+ 3 - 1
netclient/functions/mqhandlers.go

@@ -188,10 +188,12 @@ func UpdatePeers(client mqtt.Client, msg mqtt.Message) {
 		logger.Log(0, "error updating wireguard peers"+err.Error())
 		return
 	}
+	queryAddr := cfg.Node.PrimaryAddress()
+
 	//err = wireguard.SyncWGQuickConf(cfg.Node.Interface, file)
 	var iface = cfg.Node.Interface
 	if ncutils.IsMac() {
-		iface, err = local.GetMacIface(cfg.Node.Address)
+		iface, err = local.GetMacIface(queryAddr)
 		if err != nil {
 			logger.Log(0, "error retrieving mac iface: "+err.Error())
 			return

+ 1 - 1
netclient/local/routes.go

@@ -11,7 +11,7 @@ import (
 // TODO handle ipv6 in future
 
 // SetPeerRoutes - sets/removes ip routes for each peer on a network
-func SetPeerRoutes(iface, currentNodeAddr string, oldPeers map[string][]net.IPNet, newPeers []wgtypes.PeerConfig) {
+func SetPeerRoutes(iface string, oldPeers map[string][]net.IPNet, newPeers []wgtypes.PeerConfig) {
 	// traverse through all recieved peers
 	for _, peer := range newPeers {
 		// if pubkey found in existing peers, check against existing peer

+ 10 - 1
netclient/local/routes_darwin.go

@@ -4,7 +4,9 @@ import (
 	"net"
 	"strings"
 
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/seancfoley/ipaddress-go/ipaddr"
 )
 
 // route -n add -net 10.0.0.0/8 192.168.0.254
@@ -33,7 +35,14 @@ func deleteRoute(iface string, addr *net.IPNet, address string) error {
 }
 
 func setCidr(iface, address string, addr *net.IPNet) {
-	ncutils.RunCmd("route -q -n add -net "+addr.String()+" "+address, false)
+	cidr := ipaddr.NewIPAddressString(addr.String()).GetAddress()
+	if cidr.IsIPv4() {
+		ncutils.RunCmd("route -q -n add -net "+addr.String()+" "+address, false)
+	} else if cidr.IsIPv6() {
+		ncutils.RunCmd("route -A inet6 -q -n add -net "+addr.String()+" "+address, false)
+	} else {
+		logger.Log(1, "could not parse address: "+addr.String())
+	}
 }
 
 func removeCidr(iface string, addr *net.IPNet, address string) {

+ 10 - 0
netclient/local/routes_freebsd.go

@@ -3,7 +3,9 @@ package local
 import (
 	"net"
 
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/seancfoley/ipaddress-go/ipaddr"
 )
 
 func setRoute(iface string, addr *net.IPNet, address string) error {
@@ -19,6 +21,14 @@ func deleteRoute(iface string, addr *net.IPNet, address string) error {
 }
 
 func setCidr(iface, address string, addr *net.IPNet) {
+	cidr := ipaddr.NewIPAddressString(addr.String()).GetAddress()
+	if cidr.IsIPv4() {
+		ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
+	} else if cidr.IsIPv6() {
+		ncutils.RunCmd("route add -net -inet6 "+addr.String()+" -interface "+iface, false)
+	} else {
+		logger.Log(1, "could not parse address: "+addr.String())
+	}
 	ncutils.RunCmd("route add -net "+addr.String()+" -interface "+iface, false)
 }
 

+ 10 - 1
netclient/local/routes_linux.go

@@ -7,7 +7,9 @@ import (
 	"net"
 	"strings"
 
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/seancfoley/ipaddress-go/ipaddr"
 )
 
 func setRoute(iface string, addr *net.IPNet, address string) error {
@@ -28,7 +30,14 @@ func deleteRoute(iface string, addr *net.IPNet, address string) error {
 }
 
 func setCidr(iface, address string, addr *net.IPNet) {
-	ncutils.RunCmd("ip -4 route add "+addr.String()+" dev "+iface, false)
+	cidr := ipaddr.NewIPAddressString(addr.String()).GetAddress()
+	if cidr.IsIPv4() {
+		ncutils.RunCmd("ip -4 route add "+addr.String()+" dev "+iface, false)
+	} else if cidr.IsIPv6() {
+		ncutils.RunCmd("ip -6 route add "+addr.String()+" dev "+iface, false)
+	} else {
+		logger.Log(1, "could not parse address: "+addr.String())
+	}
 }
 
 func removeCidr(iface string, addr *net.IPNet, address string) {

+ 1 - 7
netclient/ncutils/iface.go

@@ -10,9 +10,9 @@ import (
 func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 	// single comparison statements
 	if newNode.Endpoint != currentNode.Endpoint ||
-		newNode.LocalAddress != currentNode.LocalAddress ||
 		newNode.PublicKey != currentNode.PublicKey ||
 		newNode.Address != currentNode.Address ||
+		newNode.Address6 != currentNode.Address6 ||
 		newNode.IsEgressGateway != currentNode.IsEgressGateway ||
 		newNode.IsIngressGateway != currentNode.IsIngressGateway ||
 		newNode.IsRelay != currentNode.IsRelay ||
@@ -27,12 +27,6 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 	}
 
 	// multi-comparison statements
-	if newNode.IsDualStack == "yes" {
-		if newNode.Address6 != currentNode.Address6 {
-			return true
-		}
-	}
-
 	if newNode.IsEgressGateway == "yes" {
 		if len(currentNode.EgressGatewayRanges) != len(newNode.EgressGatewayRanges) {
 			return true

+ 1 - 0
netclient/ncutils/peerhelper.go

@@ -10,6 +10,7 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+// GetPeers - gets the peers from a given WireGuard interface
 func GetPeers(iface string) ([]wgtypes.Peer, error) {
 
 	var peers []wgtypes.Peer

+ 36 - 19
netclient/wireguard/common.go

@@ -27,7 +27,6 @@ const (
 // SetPeers - sets peers on a given WireGuard interface
 func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error {
 	var devicePeers []wgtypes.Peer
-	var currentNodeAddr = node.Address
 	var keepalive = node.PersistentKeepalive
 	var oldPeerAllowedIps = make(map[string][]net.IPNet, len(peers))
 	var err error
@@ -68,7 +67,9 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 		var allowedips string
 		var iparr []string
 		for _, ipaddr := range peer.AllowedIPs {
-			iparr = append(iparr, ipaddr.String())
+			if len(peer.AllowedIPs) > 0 && (&ipaddr) != nil {
+				iparr = append(iparr, ipaddr.String())
+			}
 		}
 		allowedips = strings.Join(iparr, ",")
 		keepAliveString := strconv.Itoa(int(keepalive))
@@ -79,7 +80,6 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 			_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
 				" persistent-keepalive "+keepAliveString+
 				" allowed-ips "+allowedips, true)
-
 		} else {
 			_, err = ncutils.RunCmd("wg set "+iface+" peer "+peer.PublicKey.String()+
 				" endpoint "+udpendpoint+
@@ -114,7 +114,7 @@ func SetPeers(iface string, node *models.Node, peers []wgtypes.PeerConfig) error
 		err = SetMacPeerRoutes(iface)
 		return err
 	} else if ncutils.IsLinux() {
-		local.SetPeerRoutes(iface, currentNodeAddr, oldPeerAllowedIps, peers)
+		local.SetPeerRoutes(iface, oldPeerAllowedIps, peers)
 	}
 
 	return nil
@@ -146,9 +146,10 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	} else {
 		return fmt.Errorf("no interface to configure")
 	}
-	if node.Address == "" {
+	if node.PrimaryAddress() == "" {
 		return fmt.Errorf("no address to configure")
 	}
+
 	if node.UDPHolePunch == "yes" {
 		node.ListenPort = 0
 	}
@@ -160,7 +161,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	confPath := ncutils.GetNetclientPathSpecific() + ifacename + ".conf"
 	var deviceiface = ifacename
 	if ncutils.IsMac() { // if node is Mac (Darwin) get the tunnel name first
-		deviceiface, err = local.GetMacIface(node.Address)
+		deviceiface, err = local.GetMacIface(node.PrimaryAddress())
 		if err != nil || deviceiface == "" {
 			deviceiface = ifacename
 		}
@@ -174,7 +175,7 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 	ifaceReady := strings.Contains(output, deviceiface)
 	for !ifaceReady && !(time.Now().After(starttime.Add(time.Second << 4))) {
 		if ncutils.IsMac() { // if node is Mac (Darwin) get the tunnel name first
-			deviceiface, err = local.GetMacIface(node.Address)
+			deviceiface, err = local.GetMacIface(node.PrimaryAddress())
 			if err != nil || deviceiface == "" {
 				deviceiface = ifacename
 			}
@@ -208,13 +209,27 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 		}
 		time.Sleep(time.Second)
 	}
-	_, cidr, cidrErr := net.ParseCIDR(modcfg.NetworkSettings.AddressRange)
-	if cidrErr == nil {
-		local.SetCIDRRoute(ifacename, node.Address, cidr)
-	} else {
-		logger.Log(1, "could not set cidr route properly: ", cidrErr.Error())
+
+	//ipv4
+	if node.Address != "" {
+		_, cidr, cidrErr := net.ParseCIDR(modcfg.NetworkSettings.AddressRange)
+		if cidrErr == nil {
+			local.SetCIDRRoute(ifacename, node.Address, cidr)
+		} else {
+			logger.Log(1, "could not set cidr route properly: ", cidrErr.Error())
+		}
+		local.SetCurrentPeerRoutes(ifacename, node.Address, peers)
+	}
+	if node.Address6 != "" {
+		//ipv6
+		_, cidr, cidrErr := net.ParseCIDR(modcfg.NetworkSettings.AddressRange6)
+		if cidrErr == nil {
+			local.SetCIDRRoute(ifacename, node.Address6, cidr)
+		} else {
+			logger.Log(1, "could not set cidr route properly: ", cidrErr.Error())
+		}
+		local.SetCurrentPeerRoutes(ifacename, node.Address6, peers)
 	}
-	local.SetCurrentPeerRoutes(ifacename, node.Address, peers)
 
 	return err
 }
@@ -233,12 +248,14 @@ func SetWGConfig(network string, peerupdate bool) error {
 	if err != nil {
 		return err
 	}
-	var iface string
-	iface = nodecfg.Interface
-	if ncutils.IsMac() {
-		iface, err = local.GetMacIface(nodecfg.Address)
-		if err != nil {
-			return err
+	if peerupdate && !ncutils.IsFreeBSD() && !(ncutils.IsLinux() && !ncutils.IsKernel()) {
+		var iface string
+		iface = nodecfg.Interface
+		if ncutils.IsMac() {
+			iface, err = local.GetMacIface(nodecfg.PrimaryAddress())
+			if err != nil {
+				return err
+			}
 		}
 		err = SetPeers(iface, &nodecfg, []wgtypes.PeerConfig{})
 	} else if peerupdate {

+ 22 - 8
netclient/wireguard/noquick.go

@@ -52,13 +52,22 @@ func ApplyWithoutWGQuick(node *models.Node, ifacename string, confPath string) e
 		}
 	}
 
-	netmaskArr := strings.Split(node.NetworkSettings.AddressRange, "/")
-	var netmask = "32"
-	if len(netmaskArr) == 2 {
-		netmask = netmaskArr[1]
+	if node.Address != "" {
+		netmaskArr := strings.Split(node.NetworkSettings.AddressRange, "/")
+		var netmask = "32"
+		if len(netmaskArr) == 2 {
+			netmask = netmaskArr[1]
+		}
+		setKernelDevice(ifacename, node.Address, netmask)
+	}
+	if node.Address6 != "" {
+		netmaskArr := strings.Split(node.NetworkSettings.AddressRange6, "/")
+		var netmask = "128"
+		if len(netmaskArr) == 2 {
+			netmask = netmaskArr[1]
+		}
+		setKernelDevice(ifacename, node.Address6, netmask)
 	}
-	setKernelDevice(ifacename, node.Address, netmask)
-
 	_, err = wgclient.Device(ifacename)
 	if err != nil {
 		if !os.IsNotExist(err) {
@@ -88,9 +97,14 @@ func ApplyWithoutWGQuick(node *models.Node, ifacename string, confPath string) e
 		runcmds := strings.Split(node.PostUp, "; ")
 		_ = ncutils.RunCmds(runcmds, true)
 	}
-	if node.Address6 != "" && node.IsDualStack == "yes" {
+	if node.Address6 != "" {
 		logger.Log(1, "adding address: ", node.Address6)
-		_, _ = ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/64", true)
+		netmaskArr := strings.Split(node.NetworkSettings.AddressRange6, "/")
+		var netmask = "64"
+		if len(netmaskArr) == 2 {
+			netmask = netmaskArr[1]
+		}
+		ncutils.RunCmd(ipExec+" address add dev "+ifacename+" "+node.Address6+"/"+netmask, true)
 	}
 	return nil
 }

+ 1 - 1
serverctl/iptables.go

@@ -77,7 +77,7 @@ func isContainerized() bool {
 
 // make sure host allows forwarding
 func setForwardPolicy() error {
-	logger.Log(1, "setting iptables forward policy")
+	logger.Log(2, "setting iptables forward policy")
 	_, err := ncutils.RunCmd("iptables --policy FORWARD ACCEPT", false)
 	return err
 }