Browse Source

Merge pull request #274 from gravitl/feature_windows_clients

windows userspace implementation + mtu
Alex 3 years ago
parent
commit
a86047ae85

+ 2 - 0
.gitignore

@@ -10,3 +10,5 @@ netclient/netclient-arm
 netclient/netclient-arm64
 netclient/netclient-arm64
 netclient/netclient-32
 netclient/netclient-32
 config/dnsconfig/
 config/dnsconfig/
+winsw.exe
+data/

+ 8 - 2
controllers/common_test.go

@@ -28,8 +28,14 @@ func TestGetPeerList(t *testing.T) {
 		CreateNode(createnode, "skynet")
 		CreateNode(createnode, "skynet")
 		peers, err := GetPeersList("skynet")
 		peers, err := GetPeersList("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Equal(t, node.Endpoint, peers[0].Endpoint)
-		assert.Equal(t, createnode.Endpoint, peers[1].Endpoint)
+		assert.Equal(t, len(peers), 2)
+		foundNodeEndpoint := false
+		for _, peer := range peers {
+			if foundNodeEndpoint = peer.Endpoint == createnode.Endpoint; foundNodeEndpoint {
+				break
+			}
+		}
+		assert.True(t, foundNodeEndpoint)
 	})
 	})
 }
 }
 
 

+ 0 - 8
controllers/networkHttpController_test.go

@@ -83,14 +83,6 @@ func TestCreateKey(t *testing.T) {
 	var accesskey models.AccessKey
 	var accesskey models.AccessKey
 	var network models.Network
 	var network models.Network
 	network.NetID = "skynet"
 	network.NetID = "skynet"
-	t.Run("InvalidName", func(t *testing.T) {
-		network, err := GetNetwork("skynet")
-		assert.Nil(t, err)
-		accesskey.Name = "bad-name"
-		_, err = CreateAccessKey(accesskey, network)
-		assert.NotNil(t, err)
-		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'alphanum' tag")
-	})
 	t.Run("NameTooLong", func(t *testing.T) {
 	t.Run("NameTooLong", func(t *testing.T) {
 		network, err := GetNetwork("skynet")
 		network, err := GetNetwork("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)

+ 7 - 0
controllers/nodeHttpController.go

@@ -504,6 +504,9 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 
 
 func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
 func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
 	node, err := functions.GetNodeByMacAddress(gateway.NetID, gateway.NodeID)
 	node, err := functions.GetNodeByMacAddress(gateway.NetID, gateway.NodeID)
+	if node.OS == "windows" { // add in darwin later
+		return models.Node{}, errors.New(node.OS + " is unsupported for egress gateways")
+	}
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
@@ -630,6 +633,10 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 func CreateIngressGateway(netid string, macaddress string) (models.Node, error) {
 func CreateIngressGateway(netid string, macaddress string) (models.Node, error) {
 
 
 	node, err := functions.GetNodeByMacAddress(netid, macaddress)
 	node, err := functions.GetNodeByMacAddress(netid, macaddress)
+	if node.OS == "windows" { // add in darwin later
+		return models.Node{}, errors.New(node.OS + " is unsupported for ingress gateways")
+	}
+
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}

+ 10 - 5
go.mod

@@ -12,23 +12,28 @@ require (
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
 	github.com/jinzhu/copier v0.3.2 // indirect
 	github.com/jinzhu/copier v0.3.2 // indirect
+	github.com/josephspurrier/goversioninfo v1.3.0 // indirect
+	github.com/kardianos/service v1.2.0 // indirect
 	github.com/mattn/go-sqlite3 v1.14.8
 	github.com/mattn/go-sqlite3 v1.14.8
+	github.com/mdlayher/genetlink v1.0.0 // indirect
+	github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 // indirect
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.6.1
 	github.com/stretchr/testify v1.6.1
 	github.com/txn2/txeh v1.3.0
 	github.com/txn2/txeh v1.3.0
 	github.com/urfave/cli v1.22.5 // indirect
 	github.com/urfave/cli v1.22.5 // indirect
 	github.com/urfave/cli/v2 v2.3.0
 	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
-	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
+	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
-	golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
-	golang.org/x/text v0.3.5 // indirect
+	golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
-	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
+	golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 // indirect
+	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
+	golang.zx2c4.com/wireguard/windows v0.4.5 // indirect
 	google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 // indirect
 	google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 // indirect
 	google.golang.org/grpc v1.35.0
 	google.golang.org/grpc v1.35.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
 	google.golang.org/protobuf v1.26.0
 	google.golang.org/protobuf v1.26.0
+	gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
 	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
 )
 )

+ 74 - 0
go.sum

@@ -1,5 +1,7 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
+github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
 github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
 github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
@@ -89,6 +91,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/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/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -104,9 +108,21 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/josephspurrier/goversioninfo v1.3.0 h1:pmgDhWnG8I59p5kCR09J73s/gy9JqRPAtiaUK8jixtE=
+github.com/josephspurrier/goversioninfo v1.3.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
+github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
+github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
 github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw=
 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw=
 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
 github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
+github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
+github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
+github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
+github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
+github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
+github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
+github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
+github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
 github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
@@ -120,17 +136,29 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
+github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
+github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
+github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
 github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
 github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
 github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
 github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
 github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
 github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
 github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
 github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
 github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
 github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
 github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
 github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
+github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
+github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
+github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
+github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
+github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
+github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
+github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -205,6 +233,11 @@ golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -218,8 +251,17 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -244,16 +286,38 @@ golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
+golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99 h1:ZEXtoJu1S0ie/EmdYnjY3CqaCCZxnldL+K1ftMITD2Q=
+golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -261,15 +325,23 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
+golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
+golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
 golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
 golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
 golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
 golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c=
+golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
+golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
+golang.zx2c4.com/wireguard/windows v0.4.5 h1:btpw+5IM8UrSl5SILCODt1bXTM2qYpcaYArM6wDlwHA=
+golang.zx2c4.com/wireguard/windows v0.4.5/go.mod h1:LdS2bRTWu//RpztraGz5ZkPZul60cCbmgtLTPSKrS50=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -299,6 +371,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
+gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

+ 15 - 14
main.go

@@ -22,7 +22,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 )
 )
 
 
-//Start MongoDB Connection and start API Request Handler
+// Start DB Connection and start API Request Handler
 func main() {
 func main() {
 	fmt.Println(models.RetrieveLogo()) // print the logo
 	fmt.Println(models.RetrieveLogo()) // print the logo
 	initialize()                       // initial db and grpc server
 	initialize()                       // initial db and grpc server
@@ -37,19 +37,20 @@ func initialize() { // Client Mode Prereq Check
 		log.Fatal(err)
 		log.Fatal(err)
 	}
 	}
 	log.Println("database successfully connected.")
 	log.Println("database successfully connected.")
-	output, err := local.RunCmd("id -u")
-
-	if err != nil {
-		log.Println("Error running 'id -u' for prereq check. Please investigate or disable client mode.")
-		log.Fatal(output, err)
-	}
-	uid, err := strconv.Atoi(string(output[:len(output)-1]))
-	if err != nil {
-		log.Println("Error retrieving uid from 'id -u' for prereq check. Please investigate or disable client mode.")
-		log.Fatal(err)
-	}
-	if uid != 0 {
-		log.Fatal("To run in client mode requires root privileges. Either disable client mode or run with sudo.")
+	if servercfg.IsClientMode() {
+		output, err := local.RunCmd("id -u")
+		if err != nil {
+			log.Println("Error running 'id -u' for prereq check. Please investigate or disable client mode.")
+			log.Fatal(output, err)
+		}
+		uid, err := strconv.Atoi(string(output[:len(output)-1]))
+		if err != nil {
+			log.Println("Error retrieving uid from 'id -u' for prereq check. Please investigate or disable client mode.")
+			log.Fatal(err)
+		}
+		if uid != 0 {
+			log.Fatal("To run in client mode requires root privileges. Either disable client mode or run with sudo.")
+		}
 	}
 	}
 
 
 	if servercfg.IsDNSMode() {
 	if servercfg.IsDNSMode() {

+ 3 - 52
models/network.go

@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"reflect"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -41,51 +40,13 @@ type Network struct {
 	DefaultCheckInInterval int32       `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"`
 	DefaultCheckInInterval int32       `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"`
 	DefaultUDPHolePunch    string      `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
 	DefaultUDPHolePunch    string      `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
 	DefaultExtClientDNS    string      `json:"defaultextclientdns" bson:"defaultextclientdns"`
 	DefaultExtClientDNS    string      `json:"defaultextclientdns" bson:"defaultextclientdns"`
+	DefaultMTU             int32       `json:"defaultmtu" bson:"defaultmtu"`
 }
 }
 
 
 type SaveData struct { // put sensitive fields here
 type SaveData struct { // put sensitive fields here
 	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
 	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
 }
 }
 
 
-const STRING_FIELD_TYPE = "string"
-const INT64_FIELD_TYPE = "int64"
-const INT32_FIELD_TYPE = "int32"
-const ACCESS_KEY_TYPE = "[]AccessKey"
-
-var FIELD_TYPES = []string{STRING_FIELD_TYPE, INT64_FIELD_TYPE, INT32_FIELD_TYPE, ACCESS_KEY_TYPE}
-
-var FIELDS = map[string][]string{
-	// "id":                  {"ID", "string"},
-	"addressrange":        {"AddressRange", STRING_FIELD_TYPE},
-	"addressrange6":       {"AddressRange6", STRING_FIELD_TYPE},
-	"displayname":         {"DisplayName", STRING_FIELD_TYPE},
-	"netid":               {"NetID", STRING_FIELD_TYPE},
-	"nodeslastmodified":   {"NodesLastModified", INT64_FIELD_TYPE},
-	"networklastmodified": {"NetworkLastModified", INT64_FIELD_TYPE},
-	"defaultinterface":    {"DefaultInterface", STRING_FIELD_TYPE},
-	"defaultlistenport":   {"DefaultListenPort", INT32_FIELD_TYPE},
-	"nodelimit":           {"NodeLimit", INT32_FIELD_TYPE},
-	"defaultpostup":       {"DefaultPostUp", STRING_FIELD_TYPE},
-	"defaultpostdown":     {"DefaultPostDown", STRING_FIELD_TYPE},
-	"keyupdatetimestamp":  {"KeyUpdateTimeStamp", INT64_FIELD_TYPE},
-	"defaultkeepalive":    {"DefaultKeepalive", INT32_FIELD_TYPE},
-	"defaultsaveconfig":   {"DefaultSaveConfig", STRING_FIELD_TYPE},
-	"accesskeys":          {"AccessKeys", ACCESS_KEY_TYPE},
-	"allowmanualsignup":   {"AllowManualSignUp", STRING_FIELD_TYPE},
-	"islocal":             {"IsLocal", STRING_FIELD_TYPE},
-	"isdualstack":         {"IsDualStack", STRING_FIELD_TYPE},
-	"isipv4":              {"IsIPv4", STRING_FIELD_TYPE},
-	"isipv6":              {"IsIPv6", STRING_FIELD_TYPE},
-	"isgrpchub":           {"IsGRPCHub", STRING_FIELD_TYPE},
-	"localrange":          {"LocalRange", STRING_FIELD_TYPE},
-	"checkininterval":     {"DefaultCheckInInterval", INT32_FIELD_TYPE},
-	"defaultudpholepunch": {"DefaultUDPHolePunch", STRING_FIELD_TYPE},
-}
-
-func (network *Network) FieldExists(field string) bool {
-	return len(FIELDS[field]) > 0
-}
-
 func (network *Network) NetIDInNetworkCharSet() bool {
 func (network *Network) NetIDInNetworkCharSet() bool {
 
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
@@ -268,19 +229,9 @@ func (network *Network) SetDefaults() {
 		network.IsIPv6 = "no"
 		network.IsIPv6 = "no"
 		network.IsIPv4 = "yes"
 		network.IsIPv4 = "yes"
 	}
 	}
-}
 
 
-func (network *Network) CopyValues(newNetwork *Network, fieldName string) {
-	reflection := reflect.ValueOf(newNetwork)
-	value := reflect.Indirect(reflection).FieldByName(FIELDS[fieldName][0])
-	if value.IsValid() && len(FIELDS[fieldName]) == 2 {
-		fieldData := FIELDS[fieldName]
-		for _, indexVal := range FIELD_TYPES {
-			if indexVal == fieldData[1] {
-				currentReflection := reflect.ValueOf(network)
-				reflect.Indirect(currentReflection).FieldByName(FIELDS[fieldName][0]).Set(value)
-			}
-		}
+	if network.DefaultMTU == 0 {
+		network.DefaultMTU = 1280
 	}
 	}
 }
 }
 
 

+ 13 - 0
models/node.go

@@ -68,6 +68,14 @@ type Node struct {
 	LocalRange          string   `json:"localrange" bson:"localrange" yaml:"localrange"`
 	LocalRange          string   `json:"localrange" bson:"localrange" yaml:"localrange"`
 	Roaming             string   `json:"roaming" bson:"roaming" yaml:"roaming" validate:"checkyesorno"`
 	Roaming             string   `json:"roaming" bson:"roaming" yaml:"roaming" validate:"checkyesorno"`
 	IPForwarding        string   `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
 	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"`
+}
+
+func (node *Node) SetDefaultMTU() {
+	if node.MTU == 0 {
+		node.MTU = 1280
+	}
 }
 }
 
 
 func (node *Node) SetDefaulIsPending() {
 func (node *Node) SetDefaulIsPending() {
@@ -241,6 +249,7 @@ func (node *Node) SetDefaults() {
 	// == Parent Network settings ==
 	// == Parent Network settings ==
 	node.CheckInInterval = parentNetwork.DefaultCheckInInterval
 	node.CheckInInterval = parentNetwork.DefaultCheckInInterval
 	node.IsDualStack = parentNetwork.IsDualStack
 	node.IsDualStack = parentNetwork.IsDualStack
+	node.MTU = parentNetwork.DefaultMTU
 	// == node defaults if not set by parent ==
 	// == node defaults if not set by parent ==
 	node.SetIPForwardingDefault()
 	node.SetIPForwardingDefault()
 	node.SetDNSOnDefault()
 	node.SetDNSOnDefault()
@@ -259,6 +268,7 @@ func (node *Node) SetDefaults() {
 	node.SetDefaultEgressGateway()
 	node.SetDefaultEgressGateway()
 	node.SetDefaultIngressGateway()
 	node.SetDefaultIngressGateway()
 	node.SetDefaulIsPending()
 	node.SetDefaulIsPending()
+	node.SetDefaultMTU()
 	node.KeyUpdateTimeStamp = time.Now().Unix()
 	node.KeyUpdateTimeStamp = time.Now().Unix()
 }
 }
 
 
@@ -391,6 +401,9 @@ func (newNode *Node) Fill(currentNode *Node) {
 	if newNode.IsServer == "yes" {
 	if newNode.IsServer == "yes" {
 		newNode.IsStatic = "yes"
 		newNode.IsStatic = "yes"
 	}
 	}
+	if newNode.MTU == 0 {
+		newNode.MTU = currentNode.MTU
+	}
 }
 }
 
 
 func (currentNode *Node) Update(newNode *Node) error {
 func (currentNode *Node) Update(newNode *Node) error {

+ 9 - 9
models/structs.go

@@ -70,7 +70,7 @@ type SuccessResponse struct {
 }
 }
 
 
 type AccessKey struct {
 type AccessKey struct {
-	Name         string `json:"name" bson:"name" validate:"omitempty,alphanum,max=20"`
+	Name         string `json:"name" bson:"name" validate:"omitempty,max=20"`
 	Value        string `json:"value" bson:"value" validate:"omitempty,alphanum,max=16"`
 	Value        string `json:"value" bson:"value" validate:"omitempty,alphanum,max=16"`
 	AccessString string `json:"accessstring" bson:"accessstring"`
 	AccessString string `json:"accessstring" bson:"accessstring"`
 	Uses         int    `json:"uses" bson:"uses"`
 	Uses         int    `json:"uses" bson:"uses"`
@@ -98,15 +98,15 @@ type CheckInResponse struct {
 }
 }
 
 
 type PeersResponse struct {
 type PeersResponse struct {
-	PublicKey          string `json:"publickey" bson:"publickey"`
-	Endpoint           string `json:"endpoint" bson:"endpoint"`
-	Address            string `json:"address" bson:"address"`
-	Address6           string `json:"address6" bson:"address6"`
-	LocalAddress       string `json:"localaddress" bson:"localaddress"`
-	IsEgressGateway    string   `json:"isegressgateway" bson:"isegressgateway"`
+	PublicKey           string `json:"publickey" bson:"publickey"`
+	Endpoint            string `json:"endpoint" bson:"endpoint"`
+	Address             string `json:"address" bson:"address"`
+	Address6            string `json:"address6" bson:"address6"`
+	LocalAddress        string `json:"localaddress" bson:"localaddress"`
+	IsEgressGateway     string `json:"isegressgateway" bson:"isegressgateway"`
 	EgressGatewayRanges string `json:"egressgatewayrange" bson:"egressgatewayrange"`
 	EgressGatewayRanges string `json:"egressgatewayrange" bson:"egressgatewayrange"`
-	ListenPort         int32  `json:"listenport" bson:"listenport"`
-	KeepAlive          int32  `json:"persistentkeepalive" bson:"persistentkeepalive"`
+	ListenPort          int32  `json:"listenport" bson:"listenport"`
+	KeepAlive           int32  `json:"persistentkeepalive" bson:"persistentkeepalive"`
 }
 }
 
 
 type ExtPeersResponse struct {
 type ExtPeersResponse struct {

+ 8 - 10
netclient/auth/auth.go

@@ -6,6 +6,7 @@ import (
 
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 
 
 	//    "os"
 	//    "os"
 	"context"
 	"context"
@@ -19,15 +20,14 @@ import (
 
 
 // CreateJWT func will used to create the JWT while signing in and signing out
 // CreateJWT func will used to create the JWT while signing in and signing out
 func SetJWT(client nodepb.NodeServiceClient, network string) (context.Context, error) {
 func SetJWT(client nodepb.NodeServiceClient, network string) (context.Context, error) {
-	//home, err := os.UserHomeDir()
-	home := "/etc/netclient"
-	tokentext, err := ioutil.ReadFile(home + "/nettoken-" + network)
+	home := netclientutils.GetNetclientPathSpecific()
+	tokentext, err := ioutil.ReadFile(home + "nettoken-" + network)
 	if err != nil {
 	if err != nil {
 		err = AutoLogin(client, network)
 		err = AutoLogin(client, network)
 		if err != nil {
 		if err != nil {
 			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong with Auto Login: %v", err))
 			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong with Auto Login: %v", err))
 		}
 		}
-		tokentext, err = ioutil.ReadFile(home + "/nettoken-" + network)
+		tokentext, err = ioutil.ReadFile(home + "nettoken-" + network)
 		if err != nil {
 		if err != nil {
 			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong: %v", err))
 			return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong: %v", err))
 		}
 		}
@@ -42,9 +42,7 @@ func SetJWT(client nodepb.NodeServiceClient, network string) (context.Context, e
 }
 }
 
 
 func AutoLogin(client nodepb.NodeServiceClient, network string) error {
 func AutoLogin(client nodepb.NodeServiceClient, network string) error {
-	//home, err := os.UserHomeDir()
-	home := "/etc/netclient"
-	//nodecfg := config.Config.Node
+	home := netclientutils.GetNetclientPathSpecific()
 	cfg, err := config.ReadConfig(network)
 	cfg, err := config.ReadConfig(network)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -72,7 +70,7 @@ func AutoLogin(client nodepb.NodeServiceClient, network string) error {
 		return err
 		return err
 	}
 	}
 	tokenstring := []byte(res.Data)
 	tokenstring := []byte(res.Data)
-	err = ioutil.WriteFile(home+"/nettoken-"+network, tokenstring, 0644)
+	err = ioutil.WriteFile(home+"nettoken-"+network, tokenstring, 0644)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -81,12 +79,12 @@ func AutoLogin(client nodepb.NodeServiceClient, network string) error {
 
 
 func StoreSecret(key string, network string) error {
 func StoreSecret(key string, network string) error {
 	d1 := []byte(key)
 	d1 := []byte(key)
-	err := ioutil.WriteFile("/etc/netclient/secret-"+network, d1, 0644)
+	err := ioutil.WriteFile(netclientutils.GetNetclientPathSpecific()+"secret-"+network, d1, 0644)
 	return err
 	return err
 }
 }
 
 
 func RetrieveSecret(network string) (string, error) {
 func RetrieveSecret(network string) (string, error) {
-	dat, err := ioutil.ReadFile("/etc/netclient/secret-" + network)
+	dat, err := ioutil.ReadFile(netclientutils.GetNetclientPathSpecific() + "secret-" + network)
 	return string(dat), err
 	return string(dat), err
 }
 }
 
 

+ 53 - 5
netclient/command/commands.go

@@ -4,11 +4,13 @@ import (
 	"log"
 	"log"
 	"os"
 	"os"
 	"strings"
 	"strings"
+	"time"
 
 
 	nodepb "github.com/gravitl/netmaker/grpc"
 	nodepb "github.com/gravitl/netmaker/grpc"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/functions"
 	"github.com/gravitl/netmaker/netclient/functions"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
 )
 )
 
 
@@ -23,6 +25,7 @@ var (
 func Join(cfg config.ClientConfig, privateKey string) error {
 func Join(cfg config.ClientConfig, privateKey string) error {
 
 
 	err := functions.JoinNetwork(cfg, privateKey)
 	err := functions.JoinNetwork(cfg, privateKey)
+
 	if err != nil {
 	if err != nil {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 		if !strings.Contains(err.Error(), "ALREADY_INSTALLED") {
 			log.Println("Error installing: ", err)
 			log.Println("Error installing: ", err)
@@ -34,7 +37,9 @@ func Join(cfg config.ClientConfig, privateKey string) error {
 				}
 				}
 			}
 			}
 			if cfg.Daemon != "off" {
 			if cfg.Daemon != "off" {
-				err = local.RemoveSystemDServices(cfg.Network)
+				if !netclientutils.IsWindows() {
+					err = local.RemoveSystemDServices(cfg.Network)
+				}
 				if err != nil {
 				if err != nil {
 					log.Println("Error removing services: ", err)
 					log.Println("Error removing services: ", err)
 				}
 				}
@@ -44,17 +49,60 @@ func Join(cfg config.ClientConfig, privateKey string) error {
 	}
 	}
 	log.Println("joined " + cfg.Network)
 	log.Println("joined " + cfg.Network)
 	if cfg.Daemon != "off" {
 	if cfg.Daemon != "off" {
-		err = functions.InstallDaemon(cfg)
+		if netclientutils.IsWindows() {
+			err = local.CreateAndRunWindowsDaemon()
+		} else {
+			err = functions.InstallDaemon(cfg)
+		}
 	}
 	}
 	return err
 	return err
 }
 }
 
 
+func RunUserspaceDaemon() {
+	cfg := config.ClientConfig{
+		Network: "all",
+	}
+	for {
+		if err := CheckIn(cfg); err != nil {
+			// pass
+		}
+		time.Sleep(30 * time.Second)
+	}
+}
+
 func CheckIn(cfg config.ClientConfig) error {
 func CheckIn(cfg config.ClientConfig) error {
-	if cfg.Network == "all" || cfg.Network == "" {
+	var err error
+	if cfg.Network == "" {
 		log.Println("Required, '-n'. No network provided. Exiting.")
 		log.Println("Required, '-n'. No network provided. Exiting.")
 		os.Exit(1)
 		os.Exit(1)
+	} else if cfg.Network == "all" {
+		log.Println("Running CheckIn for all networks.")
+		networks, err := functions.GetNetworks()
+		if err != nil {
+			log.Println("Error retrieving networks. Exiting.")
+			return err
+		}
+		for _, network := range networks {
+			currConf, err := config.ReadConfig(network)
+			if err != nil {
+				continue
+			}
+			err = functions.CheckConfig(*currConf)
+			if err != nil {
+				log.Printf("Error checking in for "+network+" network: ", err)
+			} else {
+				log.Println("checked in successfully for " + network)
+			}
+		}
+		if len(networks) == 0 {
+			if netclientutils.IsWindows() { // Windows specific - there are no netclients, so stop daemon process
+				local.StopWindowsDaemon()
+			}
+		}
+		err = nil
+	} else {
+		err = functions.CheckConfig(cfg)
 	}
 	}
-	err := functions.CheckConfig(cfg)
 	return err
 	return err
 }
 }
 
 
@@ -68,7 +116,7 @@ func Leave(cfg config.ClientConfig) error {
 
 
 func Push(cfg config.ClientConfig) error {
 func Push(cfg config.ClientConfig) error {
 	var err error
 	var err error
-	if cfg.Network == "all" {
+	if cfg.Network == "all" || netclientutils.IsWindows() {
 		log.Println("No network selected. Running Push for all networks.")
 		log.Println("No network selected. Running Push for all networks.")
 		networks, err := functions.GetNetworks()
 		networks, err := functions.GetNetworks()
 		if err != nil {
 		if err != nil {

+ 26 - 20
netclient/config/config.go

@@ -10,6 +10,7 @@ import (
 	"os"
 	"os"
 
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
 	"gopkg.in/yaml.v3"
 	"gopkg.in/yaml.v3"
 )
 )
@@ -38,19 +39,22 @@ type ServerConfig struct {
 //reading in the env file
 //reading in the env file
 func Write(config *ClientConfig, network string) error {
 func Write(config *ClientConfig, network string) error {
 	if network == "" {
 	if network == "" {
-		err := errors.New("No network provided. Exiting.")
+		err := errors.New("no network provided - exiting")
 		return err
 		return err
 	}
 	}
-	_, err := os.Stat("/etc/netclient")
+	_, err := os.Stat(netclientutils.GetNetclientPath())
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {
-		os.Mkdir("/etc/netclient", 744)
+		os.Mkdir(netclientutils.GetNetclientPath(), 0744)
 	} else if err != nil {
 	} else if err != nil {
 		return err
 		return err
 	}
 	}
-	home := "/etc/netclient"
+	home := netclientutils.GetNetclientPathSpecific()
 
 
-	file := fmt.Sprintf(home + "/netconfig-" + network)
+	file := fmt.Sprintf(home + "netconfig-" + network)
 	f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
 	f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
+	if err != nil {
+		return err
+	}
 	defer f.Close()
 	defer f.Close()
 
 
 	err = yaml.NewEncoder(f).Encode(config)
 	err = yaml.NewEncoder(f).Encode(config)
@@ -62,21 +66,21 @@ func Write(config *ClientConfig, network string) error {
 
 
 func WriteServer(server string, accesskey string, network string) error {
 func WriteServer(server string, accesskey string, network string) error {
 	if network == "" {
 	if network == "" {
-		err := errors.New("No network provided. Exiting.")
+		err := errors.New("no network provided - exiting")
 		return err
 		return err
 	}
 	}
 	nofile := false
 	nofile := false
 	//home, err := homedir.Dir()
 	//home, err := homedir.Dir()
-	_, err := os.Stat("/etc/netclient")
+	_, err := os.Stat(netclientutils.GetNetclientPath())
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {
-		os.Mkdir("/etc/netclient", 744)
+		os.Mkdir(netclientutils.GetNetclientPath(), 0744)
 	} else if err != nil {
 	} else if err != nil {
-		fmt.Println("couldnt find or create /etc/netclient")
+		fmt.Println("couldnt find or create", netclientutils.GetNetclientPath())
 		return err
 		return err
 	}
 	}
-	home := "/etc/netclient"
+	home := netclientutils.GetNetclientPathSpecific()
 
 
-	file := fmt.Sprintf(home + "/netconfig-" + network)
+	file := fmt.Sprintf(home + "netconfig-" + network)
 	//f, err := os.Open(file)
 	//f, err := os.Open(file)
 	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666)
 	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666)
 	//f, err := ioutil.ReadFile(file)
 	//f, err := ioutil.ReadFile(file)
@@ -93,7 +97,7 @@ func WriteServer(server string, accesskey string, network string) error {
 	var cfg ClientConfig
 	var cfg ClientConfig
 
 
 	if !nofile {
 	if !nofile {
-		fmt.Println("Writing to existing config file at " + home + "/netconfig-" + network)
+		fmt.Println("Writing to existing config file at " + home + "netconfig-" + network)
 		decoder := yaml.NewDecoder(f)
 		decoder := yaml.NewDecoder(f)
 		err = decoder.Decode(&cfg)
 		err = decoder.Decode(&cfg)
 		//err = yaml.Unmarshal(f, &cfg)
 		//err = yaml.Unmarshal(f, &cfg)
@@ -127,12 +131,12 @@ func WriteServer(server string, accesskey string, network string) error {
 			return err
 			return err
 		}
 		}
 	} else {
 	} else {
-		fmt.Println("Creating new config file at " + home + "/netconfig-" + network)
+		fmt.Println("Creating new config file at " + home + "netconfig-" + network)
 
 
 		cfg.Server.GRPCAddress = server
 		cfg.Server.GRPCAddress = server
 		cfg.Server.AccessKey = accesskey
 		cfg.Server.AccessKey = accesskey
 
 
-		newf, err := os.Create(home + "/netconfig-" + network)
+		newf, err := os.Create(home + "netconfig-" + network)
 		err = yaml.NewEncoder(newf).Encode(cfg)
 		err = yaml.NewEncoder(newf).Encode(cfg)
 		defer newf.Close()
 		defer newf.Close()
 		if err != nil {
 		if err != nil {
@@ -147,8 +151,8 @@ func (config *ClientConfig) ReadConfig() {
 
 
 	nofile := false
 	nofile := false
 	//home, err := homedir.Dir()
 	//home, err := homedir.Dir()
-	home := "/etc/netclient"
-	file := fmt.Sprintf(home + "/netconfig-" + config.Network)
+	home := netclientutils.GetNetclientPathSpecific()
+	file := fmt.Sprintf(home + "netconfig-" + config.Network)
 	//f, err := os.Open(file)
 	//f, err := os.Open(file)
 	f, err := os.OpenFile(file, os.O_RDONLY, 0666)
 	f, err := os.OpenFile(file, os.O_RDONLY, 0666)
 	if err != nil {
 	if err != nil {
@@ -182,13 +186,14 @@ func ModConfig(node *models.Node) error {
 	}
 	}
 	var modconfig ClientConfig
 	var modconfig ClientConfig
 	var err error
 	var err error
-	if FileExists("/etc/netclient/netconfig-" + network) {
+	if FileExists(netclientutils.GetNetclientPathSpecific() + "netconfig-" + network) {
 		useconfig, err := ReadConfig(network)
 		useconfig, err := ReadConfig(network)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		modconfig = *useconfig
 		modconfig = *useconfig
 	}
 	}
+
 	modconfig.Node = (*node)
 	modconfig.Node = (*node)
 	err = Write(&modconfig, network)
 	err = Write(&modconfig, network)
 	return err
 	return err
@@ -290,18 +295,19 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 	cfg.OperatingSystem = c.String("operatingsystem")
 	cfg.OperatingSystem = c.String("operatingsystem")
 	cfg.Daemon = c.String("daemon")
 	cfg.Daemon = c.String("daemon")
 	cfg.Node.UDPHolePunch = c.String("udpholepunch")
 	cfg.Node.UDPHolePunch = c.String("udpholepunch")
+	cfg.Node.MTU = int32(c.Int("mtu"))
 
 
 	return cfg, privateKey, nil
 	return cfg, privateKey, nil
 }
 }
 
 
 func ReadConfig(network string) (*ClientConfig, error) {
 func ReadConfig(network string) (*ClientConfig, error) {
 	if network == "" {
 	if network == "" {
-		err := errors.New("No network provided. Exiting.")
+		err := errors.New("no network provided - exiting")
 		return nil, err
 		return nil, err
 	}
 	}
 	nofile := false
 	nofile := false
-	home := "/etc/netclient"
-	file := fmt.Sprintf(home + "/netconfig-" + network)
+	home := netclientutils.GetNetclientPathSpecific()
+	file := fmt.Sprintf(home + "netconfig-" + network)
 	f, err := os.Open(file)
 	f, err := os.Open(file)
 
 
 	if err != nil {
 	if err != nil {

+ 14 - 7
netclient/functions/checkin.go

@@ -5,14 +5,16 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"log"
 	"log"
-	"strings"
 	"os"
 	"os"
+	"runtime"
+	"strings"
 
 
 	nodepb "github.com/gravitl/netmaker/grpc"
 	nodepb "github.com/gravitl/netmaker/grpc"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
@@ -30,7 +32,7 @@ func checkIP(node *models.Node, servercfg config.ServerConfig, cliconf config.Cl
 	var err error
 	var err error
 	if node.Roaming == "yes" && node.IsStatic != "yes" {
 	if node.Roaming == "yes" && node.IsStatic != "yes" {
 		if node.IsLocal == "no" {
 		if node.IsLocal == "no" {
-			extIP, err := getPublicIP()
+			extIP, err := netclientutils.GetPublicIP()
 			if err != nil {
 			if err != nil {
 				log.Println("error encountered checking ip addresses:", err)
 				log.Println("error encountered checking ip addresses:", err)
 			}
 			}
@@ -53,7 +55,7 @@ func checkIP(node *models.Node, servercfg config.ServerConfig, cliconf config.Cl
 				ipchange = true
 				ipchange = true
 			}
 			}
 		} else {
 		} else {
-			localIP, err := getLocalIP(node.LocalRange)
+			localIP, err := netclientutils.GetLocalIP(node.LocalRange)
 			if err != nil {
 			if err != nil {
 				log.Println("error encountered checking ip addresses:", err)
 				log.Println("error encountered checking ip addresses:", err)
 			}
 			}
@@ -161,7 +163,7 @@ func Pull(network string, manual bool) (*models.Node, error) {
 	servercfg := cfg.Server
 	servercfg := cfg.Server
 	var header metadata.MD
 	var header metadata.MD
 
 
-	if cfg.Node.IPForwarding == "yes" {
+	if cfg.Node.IPForwarding == "yes" && !netclientutils.IsWindows() {
 		if err = local.SetIPForwarding(); err != nil {
 		if err = local.SetIPForwarding(); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -198,6 +200,8 @@ func Pull(network string, manual bool) (*models.Node, error) {
 	if err = json.Unmarshal([]byte(readres.Data), &resNode); err != nil {
 	if err = json.Unmarshal([]byte(readres.Data), &resNode); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	// ensure that the OS never changes
+	resNode.OS = runtime.GOOS
 	if resNode.PullChanges == "yes" || manual {
 	if resNode.PullChanges == "yes" || manual {
 		// check for interface change
 		// check for interface change
 		if cfg.Node.Interface != resNode.Interface {
 		if cfg.Node.Interface != resNode.Interface {
@@ -228,24 +232,27 @@ func Pull(network string, manual bool) (*models.Node, error) {
 	} else {
 	} else {
 		if err = wireguard.SetWGConfig(network, true); err != nil {
 		if err = wireguard.SetWGConfig(network, true); err != nil {
 			if errors.Is(err, os.ErrNotExist) {
 			if errors.Is(err, os.ErrNotExist) {
-				log.Println("readding interface")
 				return Pull(network, true)
 				return Pull(network, true)
 			} else {
 			} else {
 				return nil, err
 				return nil, err
 			}
 			}
 		}
 		}
 	}
 	}
-	setDNS(&resNode, servercfg, &cfg.Node)
+	if !netclientutils.IsWindows() {
+		setDNS(&resNode, servercfg, &cfg.Node)
+	}
 
 
 	return &resNode, err
 	return &resNode, err
 }
 }
 
 
 func Push(network string) error {
 func Push(network string) error {
 	cfg, err := config.ReadConfig(network)
 	cfg, err := config.ReadConfig(network)
-	postnode := cfg.Node
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	postnode := cfg.Node
+	// always set the OS on client
+	postnode.OS = runtime.GOOS
 	servercfg := cfg.Server
 	servercfg := cfg.Server
 	var header metadata.MD
 	var header metadata.MD
 
 

+ 41 - 167
netclient/functions/common.go

@@ -9,7 +9,6 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
 	"net"
 	"net"
-	"net/http"
 	"os/exec"
 	"os/exec"
 	"strings"
 	"strings"
 
 
@@ -18,11 +17,11 @@ import (
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/metadata"
-	//homedir "github.com/mitchellh/go-homedir"
 )
 )
 
 
 var (
 var (
@@ -45,161 +44,20 @@ func ListPorts() error {
 	return err
 	return err
 }
 }
 
 
-func GetFreePort(rangestart int32) (int32, error) {
-	wgclient, err := wgctrl.New()
-	if err != nil {
-		return 0, err
-	}
-	devices, err := wgclient.Devices()
-	if err != nil {
-		return 0, err
-	}
-	var portno int32
-	portno = 0
-	for x := rangestart; x <= 60000; x++ {
-		conflict := false
-		for _, i := range devices {
-			if int32(i.ListenPort) == x {
-				conflict = true
-				break
-			}
-		}
-		if conflict {
-			continue
-		}
-		portno = x
-		break
-	}
-	return portno, err
-}
+func getPrivateAddr() (string, error) {
 
 
-func getLocalIP(localrange string) (string, error) {
-	_, localRange, err := net.ParseCIDR(localrange)
-	if err != nil {
-		return "", err
-	}
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return "", err
-	}
 	var local string
 	var local string
-	found := false
-	for _, i := range ifaces {
-		if i.Flags&net.FlagUp == 0 {
-			continue // interface down
-		}
-		if i.Flags&net.FlagLoopback != 0 {
-			continue // loopback interface
-		}
-		addrs, err := i.Addrs()
-		if err != nil {
-			return "", err
-		}
-		for _, addr := range addrs {
-			var ip net.IP
-			switch v := addr.(type) {
-			case *net.IPNet:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			case *net.IPAddr:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			}
-		}
-	}
-	if !found || local == "" {
-		return "", errors.New("Failed to find local IP in range " + localrange)
-	}
-	return local, nil
-}
-
-func getPublicIP() (string, error) {
-
-	iplist := []string{"http://ip.client.gravitl.com", "https://ifconfig.me", "http://api.ipify.org", "http://ipinfo.io/ip"}
-	endpoint := ""
-	var err error
-	for _, ipserver := range iplist {
-		resp, err := http.Get(ipserver)
-		if err != nil {
-			continue
-		}
-		defer resp.Body.Close()
-		if resp.StatusCode == http.StatusOK {
-			bodyBytes, err := ioutil.ReadAll(resp.Body)
-			if err != nil {
-				continue
-			}
-			endpoint = string(bodyBytes)
-			break
-		}
-
-	}
-	if err == nil && endpoint == "" {
-		err = errors.New("Public Address Not Found.")
-	}
-	return endpoint, err
-}
-
-func getMacAddr() ([]string, error) {
-	ifas, err := net.Interfaces()
+	conn, err := net.Dial("udp", "8.8.8.8:80")
 	if err != nil {
 	if err != nil {
-		return nil, err
+		log.Fatal(err)
 	}
 	}
-	var as []string
-	for _, ifa := range ifas {
-		a := ifa.HardwareAddr.String()
-		if a != "" {
-			as = append(as, a)
-		}
-	}
-	return as, nil
-}
+	defer conn.Close()
 
 
-func getPrivateAddr() (string, error) {
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return "", err
-	}
-	var local string
-	found := false
-	for _, i := range ifaces {
-		if i.Flags&net.FlagUp == 0 {
-			continue // interface down
-		}
-		if i.Flags&net.FlagLoopback != 0 {
-			continue // loopback interface
-		}
-		addrs, err := i.Addrs()
-		if err != nil {
-			return "", err
-		}
-		for _, addr := range addrs {
-			var ip net.IP
-			switch v := addr.(type) {
-			case *net.IPNet:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = true
-				}
-			case *net.IPAddr:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = true
-				}
-			}
-		}
-	}
-	if !found {
-		err := errors.New("Local Address Not Found.")
-		return "", err
+	localAddr := conn.LocalAddr().(*net.UDPAddr)
+	localIP := localAddr.IP
+	local = localIP.String()
+	if local == "" {
+		err = errors.New("could not find local ip")
 	}
 	}
 	return local, err
 	return local, err
 }
 }
@@ -213,7 +71,6 @@ func needInterfaceUpdate(ctx context.Context, mac string, network string, iface
 	readres, err := wcclient.ReadNode(ctx, req, grpc.Header(&header))
 	readres, err := wcclient.ReadNode(ctx, req, grpc.Header(&header))
 	if err != nil {
 	if err != nil {
 		return false, "", err
 		return false, "", err
-		log.Fatalf("Error: %v", err)
 	}
 	}
 	var resNode models.Node
 	var resNode models.Node
 	if err := json.Unmarshal([]byte(readres.Data), &resNode); err != nil {
 	if err := json.Unmarshal([]byte(readres.Data), &resNode); err != nil {
@@ -247,6 +104,10 @@ func Uninstall() error {
 			}
 			}
 		}
 		}
 	}
 	}
+	// clean up OS specific stuff
+	if netclientutils.IsWindows() {
+		local.Cleanup()
+	}
 	return err
 	return err
 }
 }
 
 
@@ -277,6 +138,10 @@ func LeaveNetwork(network string) error {
 		if err != nil {
 		if err != nil {
 			log.Printf("Failed to authenticate: %v", err)
 			log.Printf("Failed to authenticate: %v", err)
 		} else {
 		} else {
+			if netclientutils.IsWindows() {
+				local.RemoveWindowsConf(node.Interface)
+				log.Println("removed Windows tunnel " + node.Interface)
+			}
 			node.SetID()
 			node.SetID()
 			var header metadata.MD
 			var header metadata.MD
 			_, err = wcclient.DeleteNode(
 			_, err = wcclient.DeleteNode(
@@ -306,25 +171,34 @@ func RemoveLocalInstance(cfg *config.ClientConfig, networkName string) error {
 		log.Println("Removed " + networkName + " network locally")
 		log.Println("Removed " + networkName + " network locally")
 	}
 	}
 	if cfg.Daemon != "off" {
 	if cfg.Daemon != "off" {
-		err = local.RemoveSystemDServices(networkName)
+		if netclientutils.IsWindows() {
+			// TODO: Remove job?
+		} else {
+			err = local.RemoveSystemDServices(networkName)
+		}
 	}
 	}
 	return err
 	return err
 }
 }
 
 
 func DeleteInterface(ifacename string, postdown string) error {
 func DeleteInterface(ifacename string, postdown string) error {
-	ipExec, err := exec.LookPath("ip")
-	if err != nil {
-		log.Println(err)
-	}
-	out, err := local.RunCmd(ipExec + " link del " + ifacename)
-	if err != nil {
-		log.Println(out, err)
-	}
-	if postdown != "" {
-		runcmds := strings.Split(postdown, "; ")
-		err = local.RunCmds(runcmds)
+	var err error
+	if netclientutils.IsWindows() {
+		err = local.RemoveWindowsConf(ifacename)
+	} else {
+		ipExec, err := exec.LookPath("ip")
+		if err != nil {
+			log.Println(err)
+		}
+		out, err := local.RunCmd(ipExec + " link del " + ifacename)
 		if err != nil {
 		if err != nil {
-			log.Println("Error encountered running PostDown: " + err.Error())
+			log.Println(out, err)
+		}
+		if postdown != "" {
+			runcmds := strings.Split(postdown, "; ")
+			err = local.RunCmds(runcmds)
+			if err != nil {
+				log.Println("Error encountered running PostDown: " + err.Error())
+			}
 		}
 		}
 	}
 	}
 	return err
 	return err
@@ -357,7 +231,7 @@ func List() error {
 
 
 func GetNetworks() ([]string, error) {
 func GetNetworks() ([]string, error) {
 	var networks []string
 	var networks []string
-	files, err := ioutil.ReadDir("/etc/netclient")
+	files, err := ioutil.ReadDir(netclientutils.GetNetclientPath())
 	if err != nil {
 	if err != nil {
 		return networks, err
 		return networks, err
 	}
 	}

+ 2 - 10
netclient/functions/install.go

@@ -1,8 +1,8 @@
 package functions
 package functions
 
 
 import (
 import (
-        "github.com/gravitl/netmaker/netclient/config"
-        "github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/local"
 )
 )
 
 
 func InstallDaemon(cfg config.ClientConfig) error {
 func InstallDaemon(cfg config.ClientConfig) error {
@@ -11,11 +11,3 @@ func InstallDaemon(cfg config.ClientConfig) error {
 	err = local.ConfigureSystemD(cfg.Network)
 	err = local.ConfigureSystemD(cfg.Network)
 	return err
 	return err
 }
 }
-
-func getOS() (config.ClientConfig, error) {
-
-	var cfg config.ClientConfig
-
-	return cfg, nil
-}
-

+ 18 - 28
netclient/functions/join.go

@@ -7,23 +7,20 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"log"
 	"log"
-	"math/rand"
 	"net"
 	"net"
-	"time"
 
 
-	"github.com/gravitl/netmaker/database"
 	nodepb "github.com/gravitl/netmaker/grpc"
 	nodepb "github.com/gravitl/netmaker/grpc"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/gravitl/netmaker/netclient/server"
 	"github.com/gravitl/netmaker/netclient/server"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials"
-	//homedir "github.com/mitchellh/go-homedir"
 )
 )
 
 
 func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
@@ -103,14 +100,14 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		}
 		}
 	}
 	}
 	if cfg.Node.Password == "" {
 	if cfg.Node.Password == "" {
-		cfg.Node.Password = GenPass()
+		cfg.Node.Password = netclientutils.GenPass()
 	}
 	}
 	auth.StoreSecret(cfg.Node.Password, cfg.Node.Network)
 	auth.StoreSecret(cfg.Node.Password, cfg.Node.Network)
 	if cfg.Node.Endpoint == "" {
 	if cfg.Node.Endpoint == "" {
 		if cfg.Node.IsLocal == "yes" && cfg.Node.LocalAddress != "" {
 		if cfg.Node.IsLocal == "yes" && cfg.Node.LocalAddress != "" {
 			cfg.Node.Endpoint = cfg.Node.LocalAddress
 			cfg.Node.Endpoint = cfg.Node.LocalAddress
 		} else {
 		} else {
-			cfg.Node.Endpoint, err = getPublicIP()
+			cfg.Node.Endpoint, err = netclientutils.GetPublicIP()
 			if err != nil {
 			if err != nil {
 				fmt.Println("Error setting cfg.Node.Endpoint.")
 				fmt.Println("Error setting cfg.Node.Endpoint.")
 				return err
 				return err
@@ -127,7 +124,7 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 	}
 	}
 
 
 	if cfg.Node.MacAddress == "" {
 	if cfg.Node.MacAddress == "" {
-		macs, err := getMacAddr()
+		macs, err := netclientutils.GetMacAddr()
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		} else if len(macs) == 0 {
 		} else if len(macs) == 0 {
@@ -197,7 +194,7 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 	}
 	}
 
 
 	if node.ListenPort == 0 {
 	if node.ListenPort == 0 {
-		node.ListenPort, err = GetFreePort(51821)
+		node.ListenPort, err = netclientutils.GetFreePort(51821)
 		if err != nil {
 		if err != nil {
 			fmt.Printf("Error retrieving port: %v", err)
 			fmt.Printf("Error retrieving port: %v", err)
 		}
 		}
@@ -207,7 +204,7 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		cfg.Node.DNSOn = "yes"
 		cfg.Node.DNSOn = "yes"
 	}
 	}
 	if !(cfg.Node.IsLocal == "yes") && node.IsLocal == "yes" && node.LocalRange != "" {
 	if !(cfg.Node.IsLocal == "yes") && node.IsLocal == "yes" && node.LocalRange != "" {
-		node.LocalAddress, err = getLocalIP(node.LocalRange)
+		node.LocalAddress, err = netclientutils.GetLocalIP(node.LocalRange)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -227,14 +224,19 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		fmt.Println("Node is marked as PENDING.")
 		fmt.Println("Node is marked as PENDING.")
 		fmt.Println("Awaiting approval from Admin before configuring WireGuard.")
 		fmt.Println("Awaiting approval from Admin before configuring WireGuard.")
 		if cfg.Daemon != "off" {
 		if cfg.Daemon != "off" {
-			err = local.ConfigureSystemD(cfg.Network)
+			if netclientutils.IsWindows() {
+				// handle daemon here..
+				err = local.CreateAndRunWindowsDaemon()
+			} else {
+				err = local.ConfigureSystemD(cfg.Network)
+			}
 			return err
 			return err
 		}
 		}
 	}
 	}
 	log.Println("retrieving remote peers")
 	log.Println("retrieving remote peers")
 	peers, hasGateway, gateways, err := server.GetPeers(node.MacAddress, cfg.Network, cfg.Server.GRPCAddress, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
 	peers, hasGateway, gateways, err := server.GetPeers(node.MacAddress, cfg.Network, cfg.Server.GRPCAddress, node.IsDualStack == "yes", node.IsIngressGateway == "yes")
 
 
-	if err != nil && !database.IsEmptyRecord(err) {
+	if err != nil && !netclientutils.IsEmptyRecord(err) {
 		log.Println("failed to retrieve peers", err)
 		log.Println("failed to retrieve peers", err)
 		return err
 		return err
 	}
 	}
@@ -245,7 +247,11 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 		return err
 		return err
 	}
 	}
 	if cfg.Daemon != "off" {
 	if cfg.Daemon != "off" {
-		err = local.ConfigureSystemD(cfg.Network)
+		if netclientutils.IsWindows() {
+			err = local.CreateAndRunWindowsDaemon()
+		} else {
+			err = local.ConfigureSystemD(cfg.Network)
+		}
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -253,19 +259,3 @@ func JoinNetwork(cfg config.ClientConfig, privateKey string) error {
 
 
 	return err
 	return err
 }
 }
-
-//generate an access key value
-func GenPass() string {
-
-	var seededRand *rand.Rand = rand.New(
-		rand.NewSource(time.Now().UnixNano()))
-
-	length := 16
-	charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-
-	b := make([]byte, length)
-	for i := range b {
-		b[i] = charset[seededRand.Intn(len(charset))]
-	}
-	return string(b)
-}

+ 5 - 0
netclient/local/dns.go

@@ -8,6 +8,8 @@ import (
 	//"github.com/davecgh/go-spew/spew"
 	//"github.com/davecgh/go-spew/spew"
 	"log"
 	"log"
 	"os/exec"
 	"os/exec"
+
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 )
 )
 
 
 func SetDNS(nameserver string) error {
 func SetDNS(nameserver string) error {
@@ -32,6 +34,9 @@ func SetDNS(nameserver string) error {
 }
 }
 
 
 func UpdateDNS(ifacename string, network string, nameserver string) error {
 func UpdateDNS(ifacename string, network string, nameserver string) error {
+	if netclientutils.IsWindows() {
+		return nil
+	}
 	_, err := exec.LookPath("resolvectl")
 	_, err := exec.LookPath("resolvectl")
 	if err != nil {
 	if err != nil {
 		log.Println(err)
 		log.Println(err)

+ 80 - 65
netclient/local/local.go

@@ -13,6 +13,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 )
 )
 
 
 func SetIPForwarding() error {
 func SetIPForwarding() error {
@@ -85,6 +86,9 @@ func ConfigureSystemD(network string) error {
 		}
 		}
 	*/
 	*/
 	//binarypath := path  + "/netclient"
 	//binarypath := path  + "/netclient"
+	if netclientutils.IsWindows() {
+		return nil
+	}
 	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
 	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -210,52 +214,54 @@ func isOnlyService(network string) (bool, error) {
 
 
 func RemoveSystemDServices(network string) error {
 func RemoveSystemDServices(network string) error {
 	//sysExec, err := exec.LookPath("systemctl")
 	//sysExec, err := exec.LookPath("systemctl")
+	if !netclientutils.IsWindows() {
+		fullremove, err := isOnlyService(network)
+		if err != nil {
+			log.Println(err)
+		}
 
 
-	fullremove, err := isOnlyService(network)
-	if err != nil {
-		log.Println(err)
-	}
-
-	if fullremove {
-		_, err = RunCmd("systemctl disable [email protected]")
+		if fullremove {
+			_, err = RunCmd("systemctl disable [email protected]")
+			if err != nil {
+				log.Println("Error disabling [email protected]. Please investigate.")
+				log.Println(err)
+			}
+		}
+		_, err = RunCmd("systemctl daemon-reload")
 		if err != nil {
 		if err != nil {
-			log.Println("Error disabling [email protected]. Please investigate.")
+			log.Println("Error stopping netclient-" + network + ".timer. Please investigate.")
 			log.Println(err)
 			log.Println(err)
 		}
 		}
-	}
-	_, err = RunCmd("systemctl daemon-reload")
-	if err != nil {
-		log.Println("Error stopping netclient-" + network + ".timer. Please investigate.")
-		log.Println(err)
-	}
-	_, err = RunCmd("systemctl disable netclient-" + network + ".timer")
-	if err != nil {
-		log.Println("Error disabling netclient-" + network + ".timer. Please investigate.")
-		log.Println(err)
-	}
-	if fullremove {
-		if FileExists("/etc/systemd/system/[email protected]") {
-			err = os.Remove("/etc/systemd/system/[email protected]")
+		_, err = RunCmd("systemctl disable netclient-" + network + ".timer")
+		if err != nil {
+			log.Println("Error disabling netclient-" + network + ".timer. Please investigate.")
+			log.Println(err)
 		}
 		}
+		if fullremove {
+			if FileExists("/etc/systemd/system/[email protected]") {
+				err = os.Remove("/etc/systemd/system/[email protected]")
+			}
+		}
+		if FileExists("/etc/systemd/system/netclient-" + network + ".timer") {
+			err = os.Remove("/etc/systemd/system/netclient-" + network + ".timer")
+		}
+		if err != nil {
+			log.Println("Error removing file. Please investigate.")
+			log.Println(err)
+		}
+		_, err = RunCmd("systemctl daemon-reload")
+		if err != nil {
+			log.Println("Error reloading system daemons. Please investigate.")
+			log.Println(err)
+		}
+		_, err = RunCmd("systemctl reset-failed")
+		if err != nil {
+			log.Println("Error reseting failed system services. Please investigate.")
+			log.Println(err)
+		}
+		return err
 	}
 	}
-	if FileExists("/etc/systemd/system/netclient-" + network + ".timer") {
-		err = os.Remove("/etc/systemd/system/netclient-" + network + ".timer")
-	}
-	if err != nil {
-		log.Println("Error removing file. Please investigate.")
-		log.Println(err)
-	}
-	_, err = RunCmd("systemctl daemon-reload")
-	if err != nil {
-		log.Println("Error reloading system daemons. Please investigate.")
-		log.Println(err)
-	}
-	_, err = RunCmd("systemctl reset-failed")
-	if err != nil {
-		log.Println("Error reseting failed system services. Please investigate.")
-		log.Println(err)
-	}
-	return err
+	return nil
 }
 }
 
 
 func WipeLocal(network string) error {
 func WipeLocal(network string) error {
@@ -266,47 +272,56 @@ func WipeLocal(network string) error {
 	nodecfg := cfg.Node
 	nodecfg := cfg.Node
 	ifacename := nodecfg.Interface
 	ifacename := nodecfg.Interface
 
 
-	//home, err := homedir.Dir()
-	home := "/etc/netclient"
-	if FileExists(home + "/netconfig-" + network) {
-		_ = os.Remove(home + "/netconfig-" + network)
+	home := netclientutils.GetNetclientPathSpecific()
+	if FileExists(home + "netconfig-" + network) {
+		_ = os.Remove(home + "netconfig-" + network)
 	}
 	}
-	if FileExists(home + "/nettoken-" + network) {
-		_ = os.Remove(home + "/nettoken-" + network)
+	if FileExists(home + "nettoken-" + network) {
+		_ = os.Remove(home + "nettoken-" + network)
 	}
 	}
-	if FileExists(home + "/secret-" + network) {
-		_ = os.Remove(home + "/secret-" + network)
+	if FileExists(home + "secret-" + network) {
+		_ = os.Remove(home + "secret-" + network)
 	}
 	}
-	if FileExists(home + "/wgkey-" + network) {
-		_ = os.Remove(home + "/wgkey-" + network)
+	if FileExists(home + "wgkey-" + network) {
+		_ = os.Remove(home + "wgkey-" + network)
 	}
 	}
-
-	ipExec, err := exec.LookPath("ip")
-	if err != nil {
-		return err
+	if FileExists(home + "nm-" + network + ".conf") {
+		_ = os.Remove(home + "nm-" + network + ".conf")
 	}
 	}
+
 	if ifacename != "" {
 	if ifacename != "" {
-		out, err := RunCmd(ipExec + " link del " + ifacename)
-		if err != nil {
-			log.Println(out, err)
-		}
-		if nodecfg.PostDown != "" {
-			runcmds := strings.Split(nodecfg.PostDown, "; ")
-			err = RunCmds(runcmds)
+		if netclientutils.IsWindows() {
+			if err := RemoveWindowsConf(ifacename); err == nil {
+				log.Println("removed Windows interface", ifacename)
+			}
+		} else {
+			ipExec, err := exec.LookPath("ip")
 			if err != nil {
 			if err != nil {
-				log.Println("Error encountered running PostDown: " + err.Error())
+				return err
+			}
+			out, err := RunCmd(ipExec + " link del " + ifacename)
+			if err != nil {
+				log.Println(out, err)
+			}
+			if nodecfg.PostDown != "" {
+				runcmds := strings.Split(nodecfg.PostDown, "; ")
+				err = RunCmds(runcmds)
+				if err != nil {
+					log.Println("Error encountered running PostDown: " + err.Error())
+				}
 			}
 			}
 		}
 		}
 	}
 	}
 	return err
 	return err
-
 }
 }
 
 
 func HasNetwork(network string) bool {
 func HasNetwork(network string) bool {
 
 
+	if netclientutils.IsWindows() {
+		return FileExists(netclientutils.GetNetclientPathSpecific() + "netconfig-" + network)
+	}
 	return FileExists("/etc/systemd/system/netclient-"+network+".timer") ||
 	return FileExists("/etc/systemd/system/netclient-"+network+".timer") ||
-		FileExists("/etc/netclient/netconfig-"+network)
-
+		FileExists(netclientutils.GetNetclientPathSpecific()+"netconfig-"+network)
 }
 }
 
 
 func copy(src, dst string) (int64, error) {
 func copy(src, dst string) (int64, error) {

+ 162 - 0
netclient/local/windows.go

@@ -0,0 +1,162 @@
+package local
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/gravitl/netmaker/netclient/netclientutils"
+)
+
+func IsWindowsWGInstalled() bool {
+	out, err := RunCmd("wg help")
+	if err != nil {
+		return false
+	}
+	return strings.Contains(out, "Available subcommand")
+}
+
+func ApplyWindowsConf(confPath string) error {
+	if _, err := RunCmd("wireguard.exe /installtunnelservice " + confPath); err != nil {
+		return err
+	}
+	return nil
+}
+
+func RemoveWindowsConf(ifacename string) error {
+	if _, err := RunCmd("wireguard.exe /uninstalltunnelservice " + ifacename); err != nil {
+		return err
+	}
+	return nil
+}
+
+func writeServiceConfig() error {
+	serviceConfigPath := netclientutils.GetNetclientPathSpecific() + "winsw.xml"
+	scriptString := fmt.Sprintf(`<service>
+<id>netclient</id>
+<name>Netclient</name>
+<description>Connects Windows nodes to one or more Netmaker networks.</description>
+<executable>%v</executable>
+<log mode="roll"></log>
+</service>
+`, strings.Replace(netclientutils.GetNetclientPathSpecific()+"netclient.exe", `\\`, `\`, -1))
+	if !FileExists(serviceConfigPath) {
+		err := ioutil.WriteFile(serviceConfigPath, []byte(scriptString), 0644)
+		if err != nil {
+			return err
+		}
+		netclientutils.Log("wrote the daemon config file to the Netclient directory")
+	}
+	return nil
+}
+
+// == Daemon ==
+func StopWindowsDaemon() {
+	netclientutils.Log("no networks detected, stopping Windows, Netclient daemon")
+	// stop daemon, will not overwrite
+	RunCmd(strings.Replace(netclientutils.GetNetclientPathSpecific(), `\\`, `\`, -1) + `winsw.exe stop`)
+}
+
+func RemoveWindowsDaemon() {
+	// uninstall daemon, will not restart or start another
+	RunCmd(strings.Replace(netclientutils.GetNetclientPathSpecific(), `\\`, `\`, -1) + `winsw.exe uninstall`)
+	netclientutils.Log("uninstalled Windows, Netclient daemon")
+}
+
+func copyWinswOver() error {
+
+	input, err := ioutil.ReadFile(".\\winsw.exe")
+	if err != nil {
+		netclientutils.Log("failed to find winsw.exe")
+		return err
+	}
+	if err = ioutil.WriteFile(netclientutils.GetNetclientPathSpecific()+"winsw.exe", input, 0644); err != nil {
+		netclientutils.Log("failed to copy winsw.exe to " + netclientutils.GetNetclientPath())
+		return err
+	}
+	if err = os.Remove(".\\winsw.exe"); err != nil {
+		netclientutils.Log("failed to cleanup local winsw.exe, feel free to delete it")
+		return err
+	}
+	netclientutils.Log("finished copying winsw.exe")
+	return nil
+}
+
+func downloadWinsw() error {
+	fullURLFile := "https://github.com/winsw/winsw/releases/download/v2.11.0/WinSW-x64.exe"
+	fileName := "winsw.exe"
+
+	// Create the file
+	file, err := os.Create(fileName)
+	if err != nil {
+		netclientutils.Log("could not create file on OS for Winsw")
+		return err
+	}
+	client := http.Client{
+		CheckRedirect: func(r *http.Request, via []*http.Request) error {
+			r.URL.Opaque = r.URL.Path
+			return nil
+		},
+	}
+	// Put content on file
+	netclientutils.Log("downloading service tool...")
+	resp, err := client.Get(fullURLFile)
+	if err != nil {
+		netclientutils.Log("could not GET Winsw")
+		return err
+	}
+	defer resp.Body.Close()
+
+	_, err = io.Copy(file, resp.Body)
+	if err != nil {
+		netclientutils.Log("could not mount winsw.exe")
+		return err
+	}
+	defer file.Close()
+	netclientutils.Log("finished downloading Winsw")
+	return nil
+}
+
+func CreateAndRunWindowsDaemon() error {
+
+	if !FileExists(netclientutils.GetNetclientPathSpecific() + "winsw.xml") {
+		if err := writeServiceConfig(); err != nil {
+			return err
+		}
+	}
+
+	if !FileExists(netclientutils.GetNetclientPathSpecific() + "winsw.exe") {
+		netclientutils.Log("performing first time daemon setup")
+		if !FileExists(".\\winsw.exe") {
+			err := downloadWinsw()
+			if err != nil {
+				return err
+			}
+		}
+		err := copyWinswOver()
+		if err != nil {
+			return err
+		}
+		netclientutils.Log("finished daemon setup")
+	}
+	// install daemon, will not overwrite
+	RunCmd(strings.Replace(netclientutils.GetNetclientPathSpecific(), `\\`, `\`, -1) + `winsw.exe install`)
+	// start daemon, will not restart or start another
+	RunCmd(strings.Replace(netclientutils.GetNetclientPathSpecific(), `\\`, `\`, -1) + `winsw.exe start`)
+	netclientutils.Log(strings.Replace(netclientutils.GetNetclientPathSpecific(), `\\`, `\`, -1) + `winsw.exe start`)
+	return nil
+}
+
+func Cleanup() {
+	if !FileExists(netclientutils.GetNetclientPathSpecific() + "winsw.xml") {
+		writeServiceConfig()
+	}
+	StopWindowsDaemon()
+	RemoveWindowsDaemon()
+	os.RemoveAll(netclientutils.GetNetclientPath())
+	log.Println("Netclient on Windows, uninstalled")
+}

+ 45 - 20
netclient/main.go

@@ -1,3 +1,5 @@
+//go:generate goversioninfo -icon=windowsdata/resource/netmaker.ico -manifest=netclient.exe.manifest.xml -64=true -o=netclient.syso
+
 package main
 package main
 
 
 import (
 import (
@@ -5,11 +7,15 @@ import (
 	"log"
 	"log"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
+	"os/signal"
 	"strconv"
 	"strconv"
+	"syscall"
 
 
 	"github.com/gravitl/netmaker/netclient/command"
 	"github.com/gravitl/netmaker/netclient/command"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/ncwindows"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/urfave/cli/v2"
 	"github.com/urfave/cli/v2"
 )
 )
 
 
@@ -312,30 +318,49 @@ func main() {
 		},
 		},
 	}
 	}
 
 
-	// start our application
-	out, err := local.RunCmd("id -u")
+	if netclientutils.IsWindows() {
+		ncwindows.InitWindows()
+	} else {
+		// start our application
+		out, err := local.RunCmd("id -u")
 
 
-	if err != nil {
-		log.Fatal(out, err)
-	}
-	id, err := strconv.Atoi(string(out[:len(out)-1]))
+		if err != nil {
+			log.Fatal(out, err)
+		}
+		id, err := strconv.Atoi(string(out[:len(out)-1]))
 
 
-	if err != nil {
-		log.Fatal(err)
-	}
+		if err != nil {
+			log.Fatal(err)
+		}
 
 
-	if id != 0 {
-		log.Fatal("This program must be run with elevated privileges (sudo). This program installs a SystemD service and configures WireGuard and networking rules. Please re-run with sudo/root.")
-	}
+		if id != 0 {
+			log.Fatal("This program must be run with elevated privileges (sudo). This program installs a SystemD service and configures WireGuard and networking rules. Please re-run with sudo/root.")
+		}
 
 
-	_, err = exec.LookPath("wg")
-	if err != nil {
-		log.Println(err)
-		log.Fatal("WireGuard not installed. Please install WireGuard (wireguard-tools) and try again.")
+		_, err = exec.LookPath("wg")
+		if err != nil {
+			log.Println(err)
+			log.Fatal("WireGuard not installed. Please install WireGuard (wireguard-tools) and try again.")
+		}
 	}
 	}
-
-	err = app.Run(os.Args)
-	if err != nil {
-		log.Fatal(err)
+	if netclientutils.IsWindows() {
+		if !local.IsWindowsWGInstalled() {
+			log.Fatal("Please install Windows WireGuard before using Gravitl Netclient. https://download.wireguard.com/windows-client/wireguard-installer.exe")
+		}
+	}
+	if len(os.Args) == 1 && netclientutils.IsWindows() {
+		c := make(chan os.Signal)
+		signal.Notify(c, os.Interrupt, syscall.SIGTERM)
+		go func() {
+			<-c
+			log.Println("closing Gravitl Netclient")
+			os.Exit(0)
+		}()
+		command.RunUserspaceDaemon()
+	} else {
+		err := app.Run(os.Args)
+		if err != nil {
+			log.Fatal(err)
+		}
 	}
 	}
 }
 }

+ 38 - 0
netclient/ncwindows/windows.go

@@ -0,0 +1,38 @@
+package ncwindows
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+
+	"github.com/gravitl/netmaker/netclient/netclientutils"
+)
+
+// Initialize windows directory & files and such
+func InitWindows() {
+
+	_, directoryErr := os.Stat(netclientutils.GetNetclientPath()) // Check if data directory exists or not
+	if os.IsNotExist(directoryErr) {                              // create a data directory
+		os.Mkdir(netclientutils.GetNetclientPath(), 0755)
+	}
+	wdPath, wdErr := os.Getwd() // get the current working directory
+	if wdErr != nil {
+		log.Fatal("failed to get current directory..")
+	}
+	_, dataNetclientErr := os.Stat(netclientutils.GetNetclientPathSpecific() + "netclient.exe")
+	_, currentNetclientErr := os.Stat(wdPath + "\\netclient.exe")
+	if os.IsNotExist(dataNetclientErr) { // check and see if netclient.exe is in appdata
+		if currentNetclientErr == nil { // copy it if it exists locally
+			input, err := ioutil.ReadFile(wdPath + "\\netclient.exe")
+			if err != nil {
+				log.Println("failed to find netclient.exe")
+				return
+			}
+			if err = ioutil.WriteFile(netclientutils.GetNetclientPathSpecific()+"netclient.exe", input, 0644); err != nil {
+				log.Println("failed to copy netclient.exe to", netclientutils.GetNetclientPath())
+				return
+			}
+		}
+	}
+	log.Println("Gravitl Netclient on Windows started")
+}

BIN
netclient/netclient.exe


+ 17 - 0
netclient/netclient.exe.manifest.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+    <assemblyIdentity
+            version="0.7.3.0"
+            processorArchitecture="*"
+            name="netclient.exe"
+            type="win32"
+    />
+    <description>Windows Netclient</description>
+    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+        <security>
+            <requestedPrivileges>
+                <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+            </requestedPrivileges>
+        </security>
+    </trustInfo>
+</assembly> 

BIN
netclient/netclient.syso


+ 257 - 0
netclient/netclientutils/netclientutils.go

@@ -0,0 +1,257 @@
+package netclientutils
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"math/rand"
+	"net"
+	"net/http"
+	"os"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+
+	"golang.zx2c4.com/wireguard/wgctrl"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+const NO_DB_RECORD = "no result found"
+const NO_DB_RECORDS = "could not find any records"
+const LINUX_APP_DATA_PATH = "/etc/netclient"
+const WINDOWS_APP_DATA_PATH = "C:\\ProgramData\\Netclient"
+const WINDOWS_SVC_NAME = "netclient"
+
+func Log(message string) {
+	log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile))
+	log.Println("[netclient]", message)
+}
+
+func IsWindows() bool {
+	return runtime.GOOS == "windows"
+}
+
+// == database returned nothing error ==
+func IsEmptyRecord(err error) bool {
+	if err == nil {
+		return false
+	}
+	return strings.Contains(err.Error(), NO_DB_RECORD) || strings.Contains(err.Error(), NO_DB_RECORDS)
+}
+
+//generate an access key value
+func GenPass() string {
+
+	var seededRand *rand.Rand = rand.New(
+		rand.NewSource(time.Now().UnixNano()))
+
+	length := 16
+	charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+	b := make([]byte, length)
+	for i := range b {
+		b[i] = charset[seededRand.Intn(len(charset))]
+	}
+	return string(b)
+}
+
+func GetPublicIP() (string, error) {
+
+	iplist := []string{"http://ip.client.gravitl.com", "https://ifconfig.me", "http://api.ipify.org", "http://ipinfo.io/ip"}
+	endpoint := ""
+	var err error
+	for _, ipserver := range iplist {
+		resp, err := http.Get(ipserver)
+		if err != nil {
+			continue
+		}
+		defer resp.Body.Close()
+		if resp.StatusCode == http.StatusOK {
+			bodyBytes, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				continue
+			}
+			endpoint = string(bodyBytes)
+			break
+		}
+	}
+	if err == nil && endpoint == "" {
+		err = errors.New("public address not found")
+	}
+	return endpoint, err
+}
+
+func GetMacAddr() ([]string, error) {
+	ifas, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	var as []string
+	for _, ifa := range ifas {
+		a := ifa.HardwareAddr.String()
+		if a != "" {
+			as = append(as, a)
+		}
+	}
+	return as, nil
+}
+
+func parsePeers(keepalive int32, peers []wgtypes.PeerConfig) (string, error) {
+	peersString := ""
+	if keepalive <= 0 {
+		keepalive = 20
+	}
+	for _, peer := range peers {
+		newAllowedIps := []string{}
+		for _, allowedIP := range peer.AllowedIPs {
+			newAllowedIps = append(newAllowedIps, allowedIP.String())
+		}
+		peersString += fmt.Sprintf(`[Peer]
+PublicKey = %s
+AllowedIps = %s
+Endpoint = %s
+PersistentKeepAlive = %s
+
+`,
+			peer.PublicKey.String(),
+			strings.Join(newAllowedIps, ","),
+			peer.Endpoint.String(),
+			strconv.Itoa(int(keepalive)),
+		)
+	}
+	return peersString, nil
+}
+
+func CreateUserSpaceConf(address string, privatekey string, listenPort string, mtu int32, perskeepalive int32, peers []wgtypes.PeerConfig) (string, error) {
+	peersString, err := parsePeers(perskeepalive, peers)
+	listenPortString := ""
+	if mtu <= 0 {
+		mtu = 1280
+	}
+	if listenPort != "" {
+		listenPortString += "ListenPort = " + listenPort
+	}
+	if err != nil {
+		return "", err
+	}
+	config := fmt.Sprintf(`[Interface]
+Address = %s
+PrivateKey = %s
+MTU = %s
+%s
+
+%s
+
+`,
+		address+"/32",
+		privatekey,
+		strconv.Itoa(int(mtu)),
+		listenPortString,
+		peersString)
+	return config, nil
+}
+
+func GetLocalIP(localrange string) (string, error) {
+	_, localRange, err := net.ParseCIDR(localrange)
+	if err != nil {
+		return "", err
+	}
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return "", err
+	}
+	var local string
+	found := false
+	for _, i := range ifaces {
+		if i.Flags&net.FlagUp == 0 {
+			continue // interface down
+		}
+		if i.Flags&net.FlagLoopback != 0 {
+			continue // loopback interface
+		}
+		addrs, err := i.Addrs()
+		if err != nil {
+			return "", err
+		}
+		for _, addr := range addrs {
+			var ip net.IP
+			switch v := addr.(type) {
+			case *net.IPNet:
+				if !found {
+					ip = v.IP
+					local = ip.String()
+					found = localRange.Contains(ip)
+				}
+			case *net.IPAddr:
+				if !found {
+					ip = v.IP
+					local = ip.String()
+					found = localRange.Contains(ip)
+				}
+			}
+		}
+	}
+	if !found || local == "" {
+		return "", errors.New("Failed to find local IP in range " + localrange)
+	}
+	return local, nil
+}
+
+func GetFreePort(rangestart int32) (int32, error) {
+	wgclient, err := wgctrl.New()
+	if err != nil {
+		return 0, err
+	}
+	devices, err := wgclient.Devices()
+	if err != nil {
+		return 0, err
+	}
+	var portno int32
+	portno = 0
+	for x := rangestart; x <= 60000; x++ {
+		conflict := false
+		for _, i := range devices {
+			if int32(i.ListenPort) == x {
+				conflict = true
+				break
+			}
+		}
+		if conflict {
+			continue
+		}
+		portno = x
+		break
+	}
+	return portno, err
+}
+
+// == OS PATH FUNCTIONS ==
+
+func GetHomeDirWindows() string {
+	if IsWindows() {
+		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+		if home == "" {
+			home = os.Getenv("USERPROFILE")
+		}
+		return home
+	}
+	return os.Getenv("HOME")
+}
+
+func GetNetclientPath() string {
+	if IsWindows() {
+		return WINDOWS_APP_DATA_PATH
+	} else {
+		return LINUX_APP_DATA_PATH
+	}
+}
+
+func GetNetclientPathSpecific() string {
+	if IsWindows() {
+		return WINDOWS_APP_DATA_PATH + "\\"
+	} else {
+		return LINUX_APP_DATA_PATH + "/"
+	}
+}

+ 8 - 0
netclient/resources.rc

@@ -0,0 +1,8 @@
+#include <windows.h>
+
+#pragma code_page(65001)
+
+#define STRINGIZE(x) #x
+#define EXPAND(x) STRINGIZE(x)
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST netclient.exe.manifest.xml
+wintun.dll RCDATA wintun.dll

+ 43 - 0
netclient/versioninfo.json

@@ -0,0 +1,43 @@
+{
+    "FixedFileInfo": {
+        "FileVersion": {
+            "Major": 0,
+            "Minor": 7,
+            "Patch": 3,
+            "Build": 0
+        },
+        "ProductVersion": {
+            "Major": 0,
+            "Minor": 7,
+            "Patch": 3,
+            "Build": 0
+        },
+        "FileFlagsMask": "3f",
+        "FileFlags ": "00",
+        "FileOS": "040004",
+        "FileType": "01",
+        "FileSubType": "00"
+    },
+    "StringFileInfo": {
+        "Comments": "",
+        "CompanyName": "Gravitl",
+        "FileDescription": "",
+        "FileVersion": "",
+        "InternalName": "",
+        "LegalCopyright": "",
+        "LegalTrademarks": "",
+        "OriginalFilename": "",
+        "PrivateBuild": "",
+        "ProductName": "Netclient",
+        "ProductVersion": "v0.7.3.0",
+        "SpecialBuild": ""
+    },
+    "VarFileInfo": {
+        "Translation": {
+            "LangID": "0409",
+            "CharsetID": "04B0"
+        }
+    },
+    "IconPath": "netclient.ico",
+    "ManifestPath": "netclient.exe.manifest.xml"
+}

BIN
netclient/windowsdata/resource/netmaker.ico


+ 107 - 70
netclient/wireguard/kernel.go

@@ -12,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/gravitl/netmaker/netclient/server"
 	"github.com/gravitl/netmaker/netclient/server"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -20,10 +21,6 @@ import (
 
 
 func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
 func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig, hasGateway bool, gateways []string) error {
 
 
-	ipExec, err := exec.LookPath("ip")
-	if err != nil {
-		return err
-	}
 	key, err := wgtypes.ParseKey(privkey)
 	key, err := wgtypes.ParseKey(privkey)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -64,18 +61,26 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 		network = node.Network
 		network = node.Network
 	}
 	}
 
 
-	_, delErr := local.RunCmd("ip link delete dev " + ifacename)
-	addLinkOut, addLinkErr := local.RunCmd(ipExec + " link add dev " + ifacename + " type wireguard")
-	addOut, addErr := local.RunCmd(ipExec + " address add dev " + ifacename + " " + node.Address + "/24")
-	if delErr != nil {
-		// not displaying error
-		// log.Println(delOut, delErr)
-	}
-	if addLinkErr != nil {
-		log.Println(addLinkOut, addLinkErr)
-	}
-	if addErr != nil {
-		log.Println(addOut, addErr)
+	if !netclientutils.IsWindows() {
+
+		ipExec, err := exec.LookPath("ip")
+		if err != nil {
+			return err
+		}
+
+		_, delErr := local.RunCmd("ip link delete dev " + ifacename)
+		addLinkOut, addLinkErr := local.RunCmd(ipExec + " link add dev " + ifacename + " type wireguard")
+		addOut, addErr := local.RunCmd(ipExec + " address add dev " + ifacename + " " + node.Address + "/24")
+		if delErr != nil {
+			// not displaying error
+			// log.Println(delOut, delErr)
+		}
+		if addLinkErr != nil {
+			log.Println(addLinkOut, addLinkErr)
+		}
+		if addErr != nil {
+			log.Println(addOut, addErr)
+		}
 	}
 	}
 	var nodeport int
 	var nodeport int
 	nodeport = int(node.ListenPort)
 	nodeport = int(node.ListenPort)
@@ -98,74 +103,106 @@ func InitWireguard(node *models.Node, privkey string, peers []wgtypes.PeerConfig
 			Peers:        peers,
 			Peers:        peers,
 		}
 		}
 	}
 	}
-	_, err = wgclient.Device(ifacename)
-	if err != nil {
-		if os.IsNotExist(err) {
-			fmt.Println("Device does not exist: ")
-			fmt.Println(err)
+	if netclientutils.IsWindows() {
+		var newConf string
+		if node.UDPHolePunch != "yes" {
+			newConf, _ = netclientutils.CreateUserSpaceConf(node.Address, key.String(), strconv.FormatInt(int64(node.ListenPort), 10), node.MTU, node.PersistentKeepalive, peers)
 		} else {
 		} else {
-			log.Fatalf("Unknown config error: %v", err)
+			newConf, _ = netclientutils.CreateUserSpaceConf(node.Address, key.String(), "", node.MTU, node.PersistentKeepalive, peers)
+		}
+		confPath := netclientutils.GetNetclientPathSpecific() + node.Interface + ".conf"
+		err = ioutil.WriteFile(confPath, []byte(newConf), 0644)
+		if err != nil {
+			return err
+		}
+		// spin up userspace / windows interface + apply the conf file
+		err := local.RemoveWindowsConf(node.Interface) // remove interface first
+		if err != nil {
+			log.Println("attempted to remove pre-existing windows interface before updating")
+		}
+		local.ApplyWindowsConf(confPath)
+	} else {
+		ipExec, err := exec.LookPath("ip")
+		if err != nil {
+			return err
 		}
 		}
-	}
 
 
-	err = wgclient.ConfigureDevice(ifacename, conf)
+		devices, err := wgclient.Devices()
+		if err != nil {
+			log.Println("no devices found:", err)
+		}
+		for _, d := range devices {
+			log.Println("found wg device:", d.Name)
+		}
 
 
-	if err != nil {
-		if os.IsNotExist(err) {
-			fmt.Println("Device does not exist: ")
-			fmt.Println(err)
-		} else {
-			fmt.Printf("This is inconvenient: %v", err)
+		_, err = wgclient.Device(ifacename)
+		if err != nil {
+			if os.IsNotExist(err) {
+				fmt.Println("Device does not exist: ")
+				fmt.Println(err)
+			} else {
+				log.Fatalf("Unknown config error: %v", err)
+			}
 		}
 		}
-	}
-	//=========DNS Setup==========\\
-	if nodecfg.DNSOn == "yes" {
-		_ = local.UpdateDNS(ifacename, network, nameserver)
-	}
-	//=========End DNS Setup=======\\
-	if ipLinkDownOut, err := local.RunCmd(ipExec + " link set down dev " + ifacename); err != nil {
-		log.Println(ipLinkDownOut, err)
-		return err
-	}
 
 
-	if nodecfg.PostDown != "" {
-		runcmds := strings.Split(nodecfg.PostDown, "; ")
-		err = local.RunCmds(runcmds)
+		err = wgclient.ConfigureDevice(ifacename, conf)
 		if err != nil {
 		if err != nil {
-			fmt.Println("Error encountered running PostDown: " + err.Error())
+			if os.IsNotExist(err) {
+				fmt.Println("Device does not exist: ")
+				fmt.Println(err)
+			} else {
+				fmt.Printf("This is inconvenient: %v", err)
+			}
 		}
 		}
-	}
 
 
-	if ipLinkUpOut, err := local.RunCmd(ipExec + " link set up dev " + ifacename); err != nil {
-		log.Println(ipLinkUpOut, err)
-		return err
-	}
+		//=========DNS Setup==========\\
+		if nodecfg.DNSOn == "yes" {
+			_ = local.UpdateDNS(ifacename, network, nameserver)
+		}
+		//=========End DNS Setup=======\\
+		if _, err := local.RunCmd(ipExec + " link set down dev " + ifacename); err != nil {
+			netclientutils.Log("attempted to remove interface before editing")
+			return err
+		}
 
 
-	if nodecfg.PostUp != "" {
-		runcmds := strings.Split(nodecfg.PostUp, "; ")
-		err = local.RunCmds(runcmds)
-		if err != nil {
-			fmt.Println("Error encountered running PostUp: " + err.Error())
+		if nodecfg.PostDown != "" {
+			runcmds := strings.Split(nodecfg.PostDown, "; ")
+			err = local.RunCmds(runcmds)
+			if err != nil {
+				fmt.Println("Error encountered running PostDown: " + err.Error())
+			}
 		}
 		}
-	}
-	if hasGateway {
-		for _, gateway := range gateways {
-			out, err := local.RunCmd(ipExec + " -4 route add " + gateway + " dev " + ifacename)
-			fmt.Println(string(out))
+		// set MTU of node interface
+		if _, err := local.RunCmd(ipExec + " link set mtu " + strconv.Itoa(int(nodecfg.MTU)) + " up dev " + ifacename); err != nil {
+			netclientutils.Log("failed to create interface with mtu " + ifacename)
+			return err
+		}
+
+		if nodecfg.PostUp != "" {
+			runcmds := strings.Split(nodecfg.PostUp, "; ")
+			err = local.RunCmds(runcmds)
 			if err != nil {
 			if err != nil {
-				fmt.Println("error encountered adding gateway: " + err.Error())
+				fmt.Println("Error encountered running PostUp: " + err.Error())
 			}
 			}
 		}
 		}
-	}
-	if node.Address6 != "" && node.IsDualStack == "yes" {
-		fmt.Println("adding address: " + node.Address6)
-		out, err := local.RunCmd(ipExec + " address add dev " + ifacename + " " + node.Address6 + "/64")
-		if err != nil {
-			fmt.Println(out)
-			fmt.Println("error encountered adding ipv6: " + err.Error())
+		if hasGateway {
+			for _, gateway := range gateways {
+				out, err := local.RunCmd(ipExec + " -4 route add " + gateway + " dev " + ifacename)
+				fmt.Println(string(out))
+				if err != nil {
+					fmt.Println("error encountered adding gateway: " + err.Error())
+				}
+			}
+		}
+		if node.Address6 != "" && node.IsDualStack == "yes" {
+			fmt.Println("adding address: " + node.Address6)
+			out, err := local.RunCmd(ipExec + " address add dev " + ifacename + " " + node.Address6 + "/64")
+			if err != nil {
+				fmt.Println(out)
+				fmt.Println("error encountered adding ipv6: " + err.Error())
+			}
 		}
 		}
 	}
 	}
-
 	return err
 	return err
 }
 }
 
 
@@ -314,11 +351,11 @@ func SetPeers(iface string, keepalive int32, peers []wgtypes.PeerConfig) error {
 
 
 func StorePrivKey(key string, network string) error {
 func StorePrivKey(key string, network string) error {
 	d1 := []byte(key)
 	d1 := []byte(key)
-	err := ioutil.WriteFile("/etc/netclient/wgkey-"+network, d1, 0644)
+	err := ioutil.WriteFile(netclientutils.GetNetclientPathSpecific()+"wgkey-"+network, d1, 0644)
 	return err
 	return err
 }
 }
 
 
 func RetrievePrivKey(network string) (string, error) {
 func RetrievePrivKey(network string) (string, error) {
-	dat, err := ioutil.ReadFile("/etc/netclient/wgkey-" + network)
+	dat, err := ioutil.ReadFile(netclientutils.GetNetclientPathSpecific() + "wgkey-" + network)
 	return string(dat), err
 	return string(dat), err
 }
 }

+ 0 - 1
servercfg/serverconf.go

@@ -309,7 +309,6 @@ func GetPublicIP() (string, error) {
 			endpoint = string(bodyBytes)
 			endpoint = string(bodyBytes)
 			break
 			break
 		}
 		}
-
 	}
 	}
 	if err == nil && endpoint == "" {
 	if err == nil && endpoint == "" {
 		err = errors.New("Public Address Not Found.")
 		err = errors.New("Public Address Not Found.")

+ 24 - 14
serverctl/serverctl.go

@@ -12,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/local"
+	"github.com/gravitl/netmaker/netclient/netclientutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
@@ -31,10 +32,17 @@ func GetServerWGConf() (models.IntClient, error) {
 }
 }
 
 
 func InstallNetclient() error {
 func InstallNetclient() error {
-	if !FileExists("/etc/netclient/netclient") {
-		_, err := copy("./netclient/netclient", "/etc/netclient/netclient")
+
+	netclientPath := netclientutils.GetNetclientPathSpecific()
+	if !FileExists(netclientPath + "netclient") {
+		var err error
+		if netclientutils.IsWindows() {
+			_, err = copy(".\\netclient\\netclient", netclientPath+"netclient")
+		} else {
+			_, err = copy("./netclient/netclient", netclientPath+"netclient")
+		}
 		if err != nil {
 		if err != nil {
-			log.Println("could not create /etc/netclient")
+			log.Println("could not create " + netclientPath + "netclient")
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -79,12 +87,13 @@ func copy(src, dst string) (int64, error) {
 }
 }
 
 
 func RemoveNetwork(network string) (bool, error) {
 func RemoveNetwork(network string) (bool, error) {
-	_, err := os.Stat("/etc/netclient/netclient")
+	netclientPath := netclientutils.GetNetclientPathSpecific()
+	_, err := os.Stat(netclientPath + "netclient")
 	if err != nil {
 	if err != nil {
-		log.Println("could not find /etc/netclient")
+		log.Println("could not find " + netclientPath + "netclient")
 		return false, err
 		return false, err
 	}
 	}
-	cmdoutput, err := local.RunCmd("/etc/netclient/netclient leave -n " + network)
+	cmdoutput, err := local.RunCmd(netclientPath + "netclient leave -n " + network)
 	if err != nil {
 	if err != nil {
 		log.Println(string(cmdoutput))
 		log.Println(string(cmdoutput))
 		return false, err
 		return false, err
@@ -100,12 +109,13 @@ func AddNetwork(network string) (bool, error) {
 		log.Println("could not get public IP.")
 		log.Println("could not get public IP.")
 		return false, err
 		return false, err
 	}
 	}
-
-	_, err = os.Stat("/etc/netclient")
+	netclientDir := netclientutils.GetNetclientPath()
+	netclientPath := netclientutils.GetNetclientPathSpecific()
+	_, err = os.Stat(netclientDir)
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {
-		os.Mkdir("/etc/netclient", 744)
+		os.Mkdir(netclientDir, 744)
 	} else if err != nil {
 	} else if err != nil {
-		log.Println("could not find or create /etc/netclient")
+		log.Println("could not find or create", netclientDir)
 		return false, err
 		return false, err
 	}
 	}
 	token, err := functions.CreateServerToken(network)
 	token, err := functions.CreateServerToken(network)
@@ -113,21 +123,21 @@ func AddNetwork(network string) (bool, error) {
 		log.Println("could not create server token for " + network)
 		log.Println("could not create server token for " + network)
 		return false, err
 		return false, err
 	}
 	}
-	_, err = os.Stat("/etc/netclient/netclient")
+	_, err = os.Stat(netclientPath + "netclient")
 	if os.IsNotExist(err) {
 	if os.IsNotExist(err) {
 		err = InstallNetclient()
 		err = InstallNetclient()
 		if err != nil {
 		if err != nil {
 			return false, err
 			return false, err
 		}
 		}
 	}
 	}
-	err = os.Chmod("/etc/netclient/netclient", 0755)
+	err = os.Chmod(netclientPath+"netclient", 0755)
 	if err != nil {
 	if err != nil {
 		log.Println("could not change netclient directory permissions")
 		log.Println("could not change netclient directory permissions")
 		return false, err
 		return false, err
 	}
 	}
-	functions.PrintUserLog(models.NODE_SERVER_NAME, "executing network join: "+"/etc/netclient/netclient "+"join "+"-t "+token+" -name "+models.NODE_SERVER_NAME+" -endpoint "+pubip, 0)
+	functions.PrintUserLog(models.NODE_SERVER_NAME, "executing network join: "+netclientPath+"netclient "+"join "+"-t "+token+" -name "+models.NODE_SERVER_NAME+" -endpoint "+pubip, 0)
 
 
-	joinCMD := exec.Command("/etc/netclient/netclient", "join", "-t", token, "-name", models.NODE_SERVER_NAME, "-endpoint", pubip)
+	joinCMD := exec.Command(netclientPath+"netclient", "join", "-t", token, "-name", models.NODE_SERVER_NAME, "-endpoint", pubip)
 	err = joinCMD.Start()
 	err = joinCMD.Start()
 
 
 	if err != nil {
 	if err != nil {