浏览代码

merge conflicts resolved

Abhishek Kondur 2 年之前
父节点
当前提交
5beda3b65c
共有 63 个文件被更改,包括 948 次插入866 次删除
  1. 2 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 54 3
      .github/workflows/branchtest.yml
  3. 25 3
      .github/workflows/deletedroplets.yml
  4. 1 1
      Dockerfile
  5. 1 1
      Dockerfile-quick
  6. 1 1
      README.md
  7. 1 1
      compose/docker-compose.netclient.yml
  8. 1 3
      compose/docker-compose.yml
  9. 11 2
      config/config.go
  10. 1 2
      controllers/dns_test.go
  11. 1 1
      controllers/docs.go
  12. 1 2
      controllers/ext_client.go
  13. 13 34
      controllers/hosts.go
  14. 1 9
      controllers/limits.go
  15. 11 4
      controllers/node.go
  16. 1 0
      controllers/node_test.go
  17. 38 0
      controllers/server.go
  18. 13 9
      controllers/user.go
  19. 123 0
      controllers/user_test.go
  20. 0 6
      docker/Caddyfile
  21. 0 6
      docker/Caddyfile-EE
  22. 3 18
      ee/initialize.go
  23. 13 19
      ee/license.go
  24. 20 38
      ee/types.go
  25. 6 3
      ee/util.go
  26. 6 8
      go.mod
  27. 10 14
      go.sum
  28. 1 1
      k8s/client/netclient-daemonset.yaml
  29. 1 1
      k8s/client/netclient.yaml
  30. 1 1
      k8s/server/netmaker-ui.yaml
  31. 73 8
      logic/acls/common.go
  32. 6 1
      logic/acls/nodeacls/modify.go
  33. 0 14
      logic/auth.go
  34. 2 6
      logic/dns.go
  35. 53 35
      logic/extpeers.go
  36. 4 26
      logic/gateway.go
  37. 1 1
      logic/host_test.go
  38. 114 18
      logic/hosts.go
  39. 17 179
      logic/networks.go
  40. 75 45
      logic/nodes.go
  41. 1 5
      logic/peers.go
  42. 5 46
      logic/relay.go
  43. 11 2
      logic/serverconf.go
  44. 3 0
      logic/telemetry.go
  45. 36 0
      logic/timer.go
  46. 24 0
      logic/users.go
  47. 1 1
      logic/zombie.go
  48. 5 5
      main.go
  49. 2 0
      models/api_host.go
  50. 7 5
      models/mqtt.go
  51. 8 0
      models/node.go
  52. 16 0
      models/structs.go
  53. 6 1
      mq/emqx.go
  54. 24 3
      mq/handlers.go
  55. 1 4
      mq/mq.go
  56. 7 4
      mq/publishers.go
  57. 22 18
      release.md
  58. 1 2
      scripts/nm-certs.sh
  59. 12 6
      scripts/nm-quick.sh
  60. 1 2
      scripts/nm-upgrade-0-17-1-to-0-19-0.sh
  61. 48 83
      servercfg/serverconf.go
  62. 0 154
      stun-server/stun-server.go
  63. 1 1
      swagger.yaml

+ 2 - 0
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,6 +31,8 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
+        - v0.20.4
+        - v0.20.3
         - v0.20.2
         - v0.20.2
         - v0.20.1
         - v0.20.1
         - v0.20.0
         - v0.20.0

+ 54 - 3
.github/workflows/branchtest.yml

@@ -10,12 +10,13 @@ jobs:
   skip-check:
   skip-check:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     outputs:
     outputs:
-      skip: ${{ steps.check.outputs.skip }}
+      skip: ${{ steps.skip.outputs.skip }}
     steps:
     steps:
       - id: skip
       - id: skip
         uses: fkirc/skip-duplicate-actions@v5
         uses: fkirc/skip-duplicate-actions@v5
         with:
         with:
           concurrent_skipping: 'always'
           concurrent_skipping: 'always'
+  
   getbranch:
   getbranch:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     needs: skip-check
     needs: skip-check
@@ -29,7 +30,7 @@ jobs:
           repository: gravitl/netclient
           repository: gravitl/netclient
           ref: develop
           ref: develop
       - name: check if branch exists
       - name: check if branch exists
-        id: checkbranch
+        id: getbranch 
         run: |
         run: |
           if git show-ref ${{ github.head_ref}}; then
           if git show-ref ${{ github.head_ref}}; then
             echo branch exists
             echo branch exists
@@ -39,12 +40,62 @@ jobs:
             echo "netclientbranch=develop" >> $GITHUB_OUTPUT
             echo "netclientbranch=develop" >> $GITHUB_OUTPUT
           fi
           fi
   
   
+  getserver:
+    runs-on: ubuntu-latest
+    needs: skip-check
+    if: ${{ needs.skip-check.outputs.skip != 'true' }}
+    outputs:
+      netmakerserver: ${{ steps.getserver.outputs.server }}
+    steps:
+      - name: setup ssh
+        run: |
+          mkdir -p ~/.ssh/
+          echo "$SSH_KEY" > ~/.ssh/id_devops
+          chmod 600 ~/.ssh/id_devops
+          cat >>~/.ssh/config <<END
+          Host *.clustercat.com
+            User root
+            IdentityFile ~/.ssh/id_devops
+            StrictHostKeyChecking no
+          END
+        env:
+          SSH_KEY: ${{ secrets.TESTING_SSH_KEY }}
+      - name: getserver
+        id: getserver
+        run: |
+          server=""
+          for arg in "branch1" "branch2" "branch3" "branch4" "branch5"; do
+            echo checking $arg
+            result=$( ssh root@server.${arg}.clustercat.com '~/branchtesting/check.sh') 
+            echo $result
+            if [ "$result" == "pass" ]
+            then
+              server=$arg
+              echo $server >> /tmp/server
+              break
+            fi
+          done
+          echo server is $server
+          if [ "$server" == "" ]
+          then
+            echo server not set
+            exit 1
+          fi
+          echo "netmakerserver=${ server }" >> $GITHUB_OUTPUT
+      - name: save server name
+        uses: actions/upload-artifact@v3
+        with:
+          name: server
+          path: /tmp/ping
+          retention-days: 3
+          
   terraform:
   terraform:
-    needs: getbranch
+    needs: [getbranch, getserver]
     uses: gravitl/devops/.github/workflows/terraform.yml@master
     uses: gravitl/devops/.github/workflows/terraform.yml@master
     with:
     with:
       netmakerbranch: ${{ github.head_ref }}
       netmakerbranch: ${{ github.head_ref }}
       netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
       netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
+      server: ${{ needs.getserver.outputs.netmakerserver }}
     secrets: inherit
     secrets: inherit
 
 
 
 

+ 25 - 3
.github/workflows/deletedroplets.yml

@@ -16,6 +16,9 @@ jobs:
         with:
         with:
           run_id: ${{ github.event.workflow_run.id}}
           run_id: ${{ github.event.workflow_run.id}}
           if_no_artifact_found: warn
           if_no_artifact_found: warn
+      - name: get server name
+        run: |
+          echo "SERVER=$(cat ./server/server) >> $GITHUB_ENV"
       - name: discord success message
       - name: discord success message
         uses: appleboy/discord-action@master
         uses: appleboy/discord-action@master
         with:
         with:
@@ -23,7 +26,7 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#42f545"
           color: "#42f545"
           username: "GitHub Bot"
           username: "GitHub Bot"
-          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} was successful: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}) will be deleted in 15 min"
+          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} on dashboard.${{ env.SERVER }}.clustercat.com was successful: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}) will be deleted in 15 min"
           file: ./results/results.log
           file: ./results/results.log
       - name: delete droplets
       - name: delete droplets
         if: success() || failure()
         if: success() || failure()
@@ -36,6 +39,14 @@ jobs:
         env:
         env:
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
+      - name: mark server as available
+        uses: appleboy/ssh-action@master
+        with:
+          host: server.${{ env.SERVER }}.clustercat.com
+          username: root
+          key: ${{ secrets.TESTING_SSH_KEY }}
+          script: |
+            rm /tmp/branchtest
 
 
   on-failure:
   on-failure:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
@@ -46,6 +57,9 @@ jobs:
         with:
         with:
           run_id: ${{ github.event.workflow_run.id}}
           run_id: ${{ github.event.workflow_run.id}}
           if_no_artifact_found: warn
           if_no_artifact_found: warn
+      - name: get server name
+        run: |
+          echo "SERVER=$(cat ./server/server) >> $GITHUB_ENV"
       - name: discord failure message
       - name: discord failure message
         uses: appleboy/discord-action@master
         uses: appleboy/discord-action@master
         with:
         with:
@@ -53,7 +67,7 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#990000"
           color: "#990000"
           username: "GitHub Bot"
           username: "GitHub Bot"
-          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt}}) will be deleted in 5 hours"
+          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt}}) will be deleted in 3 hours"
           file: ./results/results.log
           file: ./results/results.log
       - name: discord error message
       - name: discord error message
         uses: appleboy/discord-action@master
         uses: appleboy/discord-action@master
@@ -67,7 +81,7 @@ jobs:
       - name: delete droplets
       - name: delete droplets
         if: success() || failure()
         if: success() || failure()
         run: |
         run: |
-          sleep 5h
+          sleep 3h
           curl -X GET \
           curl -X GET \
             -H "Content-Type: application/json" \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
@@ -75,3 +89,11 @@ jobs:
         env:
         env:
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
           TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
+      - name: mark server as available
+        uses: appleboy/ssh-action@master
+        with:
+          host: server.${{ env.SERVER }}.clustercat.com
+          username: root
+          key: ${{ secrets.TESTING_SSH_KEY }}
+          script: |
+            rm /tmp/branchtest

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ COPY . .
 
 
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.18.0
+FROM alpine:3.18.2
 
 
 # add a c lib
 # add a c lib
 # set the working directory
 # set the working directory

+ 1 - 1
Dockerfile-quick

@@ -1,5 +1,5 @@
 #first stage - builder
 #first stage - builder
-FROM alpine:3.18.0
+FROM alpine:3.18.2
 ARG version 
 ARG version 
 WORKDIR /app
 WORKDIR /app
 COPY ./netmaker /root/netmaker
 COPY ./netmaker /root/netmaker

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.20.2-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.20.4-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 1 - 1
compose/docker-compose.netclient.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netclient:
   netclient:
     container_name: netclient
     container_name: netclient
-    image: 'gravitl/netclient:v0.20.2'
+    image: 'gravitl/netclient:v0.20.4'
     hostname: netmaker-1
     hostname: netmaker-1
     network_mode: host
     network_mode: host
     restart: on-failure
     restart: on-failure

+ 1 - 3
compose/docker-compose.yml

@@ -12,7 +12,7 @@ services:
       - sqldata:/root/data
       - sqldata:/root/data
     environment:
     environment:
       # config-dependant vars
       # config-dependant vars
-      - STUN_LIST=stun.${NM_DOMAIN}:${STUN_PORT},stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
+      - STUN_LIST=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
       # The domain/host IP indicating the mq broker address
       # The domain/host IP indicating the mq broker address
       - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}
       - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}
       # The base domain of netmaker
       # The base domain of netmaker
@@ -26,8 +26,6 @@ services:
       - TURN_SERVER_HOST=turn.${NM_DOMAIN}
       - TURN_SERVER_HOST=turn.${NM_DOMAIN}
       # domain of the turn api server
       # domain of the turn api server
       - TURN_SERVER_API_HOST=https://turnapi.${NM_DOMAIN}
       - TURN_SERVER_API_HOST=https://turnapi.${NM_DOMAIN}
-    ports:
-      - "3478:3478/udp"
 
 
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui

+ 11 - 2
config/config.go

@@ -73,14 +73,23 @@ type ServerConfig struct {
 	LicenseValue               string `yaml:"license_value"`
 	LicenseValue               string `yaml:"license_value"`
 	NetmakerAccountID          string `yaml:"netmaker_account_id"`
 	NetmakerAccountID          string `yaml:"netmaker_account_id"`
 	IsEE                       string `yaml:"is_ee"`
 	IsEE                       string `yaml:"is_ee"`
-	StunPort                   int    `yaml:"stun_port"`
-	StunList                   string `yaml:"stun_list"`
 	TurnServer                 string `yaml:"turn_server"`
 	TurnServer                 string `yaml:"turn_server"`
 	TurnApiServer              string `yaml:"turn_api_server"`
 	TurnApiServer              string `yaml:"turn_api_server"`
 	TurnPort                   int    `yaml:"turn_port"`
 	TurnPort                   int    `yaml:"turn_port"`
 	TurnUserName               string `yaml:"turn_username"`
 	TurnUserName               string `yaml:"turn_username"`
 	TurnPassword               string `yaml:"turn_password"`
 	TurnPassword               string `yaml:"turn_password"`
 	UseTurn                    bool   `yaml:"use_turn"`
 	UseTurn                    bool   `yaml:"use_turn"`
+	UsersLimit                 int    `yaml:"user_limit"`
+	ClientsLimit               int    `yaml:"client_limit"`
+	NetworksLimit              int    `yaml:"network_limit"`
+	HostsLimit                 int    `yaml:"host_limit"`
+	DeployedByOperator         bool   `yaml:"deployed_by_operator"`
+}
+
+// ProxyMode - default proxy mode for server
+type ProxyMode struct {
+	Set   bool
+	Value bool
 }
 }
 
 
 // SQLConfig - Generic SQL Config
 // SQLConfig - Generic SQL Config

+ 1 - 2
controllers/dns_test.go

@@ -51,8 +51,7 @@ func TestGetNodeDNS(t *testing.T) {
 	createNet()
 	createNet()
 	createHost()
 	createHost()
 	t.Run("NoNodes", func(t *testing.T) {
 	t.Run("NoNodes", func(t *testing.T) {
-		dns, err := logic.GetNodeDNS("skynet")
-		assert.EqualError(t, err, "could not find any records")
+		dns, _ := logic.GetNodeDNS("skynet")
 		assert.Equal(t, []models.DNSEntry(nil), dns)
 		assert.Equal(t, []models.DNSEntry(nil), dns)
 	})
 	})
 	t.Run("NodeExists", func(t *testing.T) {
 	t.Run("NodeExists", func(t *testing.T) {

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //
 //	Schemes: https
 //	Schemes: https
 //	BasePath: /
 //	BasePath: /
-//	Version: 0.20.2
+//	Version: 0.20.4
 //	Host: netmaker.io
 //	Host: netmaker.io
 //
 //
 //	Consumes:
 //	Consumes:

+ 1 - 2
controllers/ext_client.go

@@ -10,7 +10,6 @@ import (
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/pro"
 	"github.com/gravitl/netmaker/logic/pro"
@@ -102,7 +101,7 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) {
 	clients := []models.ExtClient{}
 	clients := []models.ExtClient{}
 	var err error
 	var err error
 	if len(networksSlice) > 0 && networksSlice[0] == logic.ALL_NETWORK_ACCESS {
 	if len(networksSlice) > 0 && networksSlice[0] == logic.ALL_NETWORK_ACCESS {
-		clients, err = functions.GetAllExtClients()
+		clients, err = logic.GetAllExtClients()
 		if err != nil && !database.IsEmptyRecord(err) {
 		if err != nil && !database.IsEmptyRecord(err) {
 			logger.Log(0, "failed to get all extclients: ", err.Error())
 			logger.Log(0, "failed to get all extclients: ", err.Error())
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

+ 13 - 34
controllers/hosts.go

@@ -48,38 +48,8 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	//isMasterAdmin := r.Header.Get("ismaster") == "yes"
-	//user, err := logic.GetUser(r.Header.Get("user"))
-	//if err != nil && !isMasterAdmin {
-	//	logger.Log(0, r.Header.Get("user"), "failed to fetch user: ", err.Error())
-	//	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-	//	return
-	//}
-	// return JSON/API formatted hosts
-	//ret := []models.ApiHost{}
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])
 	logger.Log(2, r.Header.Get("user"), "fetched all hosts")
 	logger.Log(2, r.Header.Get("user"), "fetched all hosts")
-	//for _, host := range apiHosts {
-	//	nodes := host.Nodes
-	//	// work on the copy
-	//	host.Nodes = []string{}
-	//	for _, nid := range nodes {
-	//		node, err := logic.GetNodeByID(nid)
-	//		if err != nil {
-	//			logger.Log(0, r.Header.Get("user"), "failed to fetch node: ", err.Error())
-	//			// TODO find the reason for the DB error, skip this node for now
-	//			continue
-	//		}
-	//		if !isMasterAdmin && !logic.UserHasNetworksAccess([]string{node.Network}, user) {
-	//			continue
-	//		}
-	//		host.Nodes = append(host.Nodes, nid)
-	//	}
-	//	// add to the response only if has perms to some nodes / networks
-	//	if len(host.Nodes) > 0 {
-	//		ret = append(ret, host)
-	//	}
-	//}
 	logic.SortApiHosts(apiHosts[:])
 	logic.SortApiHosts(apiHosts[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiHosts)
 	json.NewEncoder(w).Encode(apiHosts)
@@ -110,7 +80,14 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	hPU, err := logic.GetPeerUpdateForHost(host)
+
+	allNodes, err := logic.GetAllNodes()
+	if err != nil {
+		logger.Log(0, "could not pull peers for host", hostID)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	hPU, err := logic.GetPeerUpdateForHost(host, allNodes)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "could not pull peers for host", hostID)
 		logger.Log(0, "could not pull peers for host", hostID)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -221,6 +198,8 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 func deleteHost(w http.ResponseWriter, r *http.Request) {
 func deleteHost(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	hostid := params["hostid"]
 	hostid := params["hostid"]
+	forceDelete := r.URL.Query().Get("force") == "true"
+
 	// confirm host exists
 	// confirm host exists
 	currHost, err := logic.GetHost(hostid)
 	currHost, err := logic.GetHost(hostid)
 	if err != nil {
 	if err != nil {
@@ -228,7 +207,7 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	if err = logic.RemoveHost(currHost); err != nil {
+	if err = logic.RemoveHost(currHost, forceDelete); err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error())
 		logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
@@ -313,6 +292,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	hostid := params["hostid"]
 	hostid := params["hostid"]
 	network := params["network"]
 	network := params["network"]
+	forceDelete := r.URL.Query().Get("force") == "true"
 	if hostid == "" || network == "" {
 	if hostid == "" || network == "" {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("hostid or network cannot be empty"), "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("hostid or network cannot be empty"), "badrequest"))
 		return
 		return
@@ -334,12 +314,11 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 	node.Action = models.NODE_DELETE
 	node.Action = models.NODE_DELETE
 	node.PendingDelete = true
 	node.PendingDelete = true
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
 	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
-	if err := logic.DeleteNode(node, false); err != nil {
+	if err := logic.DeleteNode(node, forceDelete); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 		return
 	}
 	}
 	// notify node change
 	// notify node change
-
 	runUpdates(node, false)
 	runUpdates(node, false)
 	go func() { // notify of peer change
 	go func() { // notify of peer change
 		clients, err := logic.GetNetworkClients(network)
 		clients, err := logic.GetNetworkClients(network)

+ 1 - 9
controllers/limits.go

@@ -6,7 +6,6 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 // limit consts
 // limit consts
@@ -23,20 +22,13 @@ func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc {
 			Code: http.StatusForbidden, Message: "free tier limits exceeded on networks",
 			Code: http.StatusForbidden, Message: "free tier limits exceeded on networks",
 		}
 		}
 
 
-		if logic.Free_Tier && servercfg.Is_EE { // check that free tier limits not exceeded
+		if logic.Free_Tier { // check that free tier limits not exceeded
 			if limit_choice == networks_l {
 			if limit_choice == networks_l {
 				currentNetworks, err := logic.GetNetworks()
 				currentNetworks, err := logic.GetNetworks()
 				if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
 				if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					logic.ReturnErrorResponse(w, r, errorResponse)
 					return
 					return
 				}
 				}
-			} else if limit_choice == node_l {
-				nodes, err := logic.GetAllNodes()
-				if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= logic.Node_Limit {
-					errorResponse.Message = "free tier limits exceeded on nodes"
-					logic.ReturnErrorResponse(w, r, errorResponse)
-					return
-				}
 			} else if limit_choice == users_l {
 			} else if limit_choice == users_l {
 				users, err := logic.GetUsers()
 				users, err := logic.GetUsers()
 				if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {
 				if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {

+ 11 - 4
controllers/node.go

@@ -740,6 +740,7 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	// get params
 	// get params
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	var nodeid = params["nodeid"]
 	var nodeid = params["nodeid"]
+	forceDelete := r.URL.Query().Get("force") == "true"
 	fromNode := r.Header.Get("requestfrom") == "node"
 	fromNode := r.Header.Get("requestfrom") == "node"
 	node, err := logic.GetNodeByID(nodeid)
 	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
 	if err != nil {
@@ -759,10 +760,6 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	if err := logic.DeleteNode(&node, fromNode); err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
-		return
-	}
 	if node.IsRelayed {
 	if node.IsRelayed {
 		// cleanup node from relayednodes on relay node
 		// cleanup node from relayednodes on relay node
 		relayNode, err := logic.GetNodeByID(node.RelayedBy)
 		relayNode, err := logic.GetNodeByID(node.RelayedBy)
@@ -779,6 +776,16 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 
 
 	}
 	}
+	if node.IsRelay {
+		// unset all the relayed nodes
+		logic.SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
+	}
+	purge := forceDelete || fromNode
+	if err := logic.DeleteNode(&node, purge); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
+		return
+	}
+
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	if !fromNode { // notify node change
 	if !fromNode { // notify node change

+ 1 - 0
controllers/node_test.go

@@ -217,6 +217,7 @@ func TestNodeACLs(t *testing.T) {
 }
 }
 
 
 func deleteAllNodes() {
 func deleteAllNodes() {
+	logic.ClearNodeCache()
 	database.DeleteAllRecords(database.NODES_TABLE_NAME)
 	database.DeleteAllRecords(database.NODES_TABLE_NAME)
 }
 }
 
 

+ 38 - 0
controllers/server.go

@@ -22,6 +22,38 @@ func serverHandlers(r *mux.Router) {
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
+}
+func getUsage(w http.ResponseWriter, r *http.Request) {
+	type usage struct {
+		Hosts    int `json:"hosts"`
+		Clients  int `json:"clients"`
+		Networks int `json:"networks"`
+		Users    int `json:"users"`
+	}
+	var serverUsage usage
+	hosts, err := logic.GetAllHosts()
+	if err == nil {
+		serverUsage.Hosts = len(hosts)
+	}
+	clients, err := logic.GetAllExtClients()
+	if err == nil {
+		serverUsage.Clients = len(clients)
+	}
+	users, err := logic.GetUsers()
+	if err == nil {
+		serverUsage.Users = len(users)
+	}
+	networks, err := logic.GetNetworks()
+	if err == nil {
+		serverUsage.Networks = len(networks)
+	}
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(models.SuccessResponse{
+		Code:     http.StatusOK,
+		Response: serverUsage,
+	})
+
 }
 }
 
 
 // swagger:route GET /api/server/status server getStatus
 // swagger:route GET /api/server/status server getStatus
@@ -41,6 +73,12 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
 	type status struct {
 	type status struct {
 		DB     bool `json:"db_connected"`
 		DB     bool `json:"db_connected"`
 		Broker bool `json:"broker_connected"`
 		Broker bool `json:"broker_connected"`
+		Usage  struct {
+			Hosts    int `json:"hosts"`
+			Clients  int `json:"clients"`
+			Networks int `json:"networks"`
+			Users    int `json:"users"`
+		} `json:"usage"`
 	}
 	}
 
 
 	currentServerStatus := status{
 	currentServerStatus := status{

+ 13 - 9
controllers/user.go

@@ -19,6 +19,9 @@ var (
 	upgrader = websocket.Upgrader{}
 	upgrader = websocket.Upgrader{}
 )
 )
 
 
+// verifyJWT makes logic.VerifyJWT fakeable/mockable in tests
+var verifyJWT = logic.VerifyJWT
+
 func userHandlers(r *mux.Router) {
 func userHandlers(r *mux.Router) {
 
 
 	r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
 	r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods(http.MethodGet)
@@ -152,7 +155,7 @@ func getUser(w http.ResponseWriter, r *http.Request) {
 
 
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	usernameFetched := params["username"]
 	usernameFetched := params["username"]
-	user, err := logic.GetUser(usernameFetched)
+	user, err := logic.GetReturnUser(usernameFetched)
 
 
 	if err != nil {
 	if err != nil {
 		logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error())
 		logger.Log(0, usernameFetched, "failed to fetch user: ", err.Error())
@@ -230,7 +233,7 @@ func createAdmin(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	logger.Log(1, admin.UserName, "was made a new admin")
 	logger.Log(1, admin.UserName, "was made a new admin")
-	json.NewEncoder(w).Encode(admin)
+	json.NewEncoder(w).Encode(logic.ToReturnUser(admin))
 }
 }
 
 
 // swagger:route POST /api/users/{username} user createUser
 // swagger:route POST /api/users/{username} user createUser
@@ -264,7 +267,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	logger.Log(1, user.UserName, "was created")
 	logger.Log(1, user.UserName, "was created")
-	json.NewEncoder(w).Encode(user)
+	json.NewEncoder(w).Encode(logic.ToReturnUser(user))
 }
 }
 
 
 // swagger:route PUT /api/users/networks/{username} user updateUserNetworks
 // swagger:route PUT /api/users/networks/{username} user updateUserNetworks
@@ -314,12 +317,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	logger.Log(1, username, "status was updated")
 	logger.Log(1, username, "status was updated")
 	// re-read and return the new user struct
 	// re-read and return the new user struct
-	if userChange, err = logic.GetUser(username); err != nil {
+	returnUser, err := logic.GetReturnUser(username)
+	if err != nil {
 		logger.Log(0, username, "failed to fetch user: ", err.Error())
 		logger.Log(0, username, "failed to fetch user: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	json.NewEncoder(w).Encode(userChange)
+	json.NewEncoder(w).Encode(returnUser)
 }
 }
 
 
 // swagger:route PUT /api/users/{username} user updateUser
 // swagger:route PUT /api/users/{username} user updateUser
@@ -337,7 +341,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	// start here
 	// start here
-	jwtUser, _, isadmin, err := logic.VerifyJWT(r.Header.Get("Authorization"))
+	jwtUser, _, isadmin, err := verifyJWT(r.Header.Get("Authorization"))
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "verifyJWT error", err.Error())
 		logger.Log(0, "verifyJWT error", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -385,7 +389,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	logger.Log(1, username, "was updated")
 	logger.Log(1, username, "was updated")
-	json.NewEncoder(w).Encode(user)
+	json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
 }
 }
 
 
 // swagger:route PUT /api/users/{username}/adm user updateUserAdm
 // swagger:route PUT /api/users/{username}/adm user updateUserAdm
@@ -409,7 +413,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	if auth.IsOauthUser(user) != nil {
+	if auth.IsOauthUser(user) == nil {
 		err := fmt.Errorf("cannot update user info for oauth user %s", username)
 		err := fmt.Errorf("cannot update user info for oauth user %s", username)
 		logger.Log(0, err.Error())
 		logger.Log(0, err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden"))
@@ -436,7 +440,7 @@ func updateUserAdm(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	logger.Log(1, username, "was updated (admin)")
 	logger.Log(1, username, "was updated (admin)")
-	json.NewEncoder(w).Encode(user)
+	json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
 }
 }
 
 
 // swagger:route DELETE /api/users/{username} user deleteUser
 // swagger:route DELETE /api/users/{username} user deleteUser

+ 123 - 0
controllers/user_test.go

@@ -1,6 +1,12 @@
 package controller
 package controller
 
 
 import (
 import (
+	"bytes"
+	"github.com/go-jose/go-jose/v3/json"
+	"github.com/gorilla/mux"
+	"io"
+	"net/http"
+	"net/http/httptest"
 	"testing"
 	"testing"
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
@@ -19,6 +25,123 @@ func deleteAllUsers(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestGetUserNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	user := models.User{UserName: "freddie", Password: "password"}
+	haveOnlyOneUser(t, user)
+
+	// prepare request
+	rec, req := prepareUserRequest(t, models.User{}, user.UserName)
+
+	// test response
+	getUser(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user.UserName)
+}
+
+func TestCreateAdminNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	deleteAllUsers(t)
+
+	// prepare request
+	user := models.User{UserName: "jonathan", Password: "password"}
+	rec, req := prepareUserRequest(t, user, "")
+
+	// test response
+	createAdmin(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user.UserName)
+}
+
+func TestCreateUserNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	deleteAllUsers(t)
+
+	// prepare request
+	user := models.User{UserName: "jonathan", Password: "password"}
+	rec, req := prepareUserRequest(t, user, "")
+
+	// test response
+	createUser(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user.UserName)
+}
+
+func TestUpdateUserNetworksNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	user1 := models.User{UserName: "joestar", Password: "jonathan"}
+	haveOnlyOneUser(t, user1)
+
+	// prepare request
+	user2 := models.User{UserName: "joestar", Password: "joseph"}
+	rec, req := prepareUserRequest(t, user2, user1.UserName)
+
+	// test response
+	updateUserNetworks(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user1.UserName)
+}
+
+func TestUpdateUserNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	user1 := models.User{UserName: "dio", Password: "brando"}
+	haveOnlyOneUser(t, user1)
+
+	// prepare request
+	user2 := models.User{UserName: "giorno", Password: "giovanna"}
+	rec, req := prepareUserRequest(t, user2, user1.UserName)
+
+	// mock the jwt verification
+	oldVerify := verifyJWT
+	verifyJWT = func(bearerToken string) (username string, networks []string, isadmin bool, err error) {
+		return user1.UserName, user1.Networks, user1.IsAdmin, nil
+	}
+	defer func() { verifyJWT = oldVerify }()
+
+	// test response
+	updateUser(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user2.UserName)
+}
+
+func TestUpdateUserAdmNoHashedPassword(t *testing.T) {
+	// prepare existing user base
+	user1 := models.User{UserName: "dio", Password: "brando", IsAdmin: true}
+	haveOnlyOneUser(t, user1)
+
+	// prepare request
+	user2 := models.User{UserName: "giorno", Password: "giovanna"}
+	rec, req := prepareUserRequest(t, user2, user1.UserName)
+
+	// test response
+	updateUserAdm(rec, req)
+	assertUserNameButNoPassword(t, rec.Body, user2.UserName)
+}
+
+func prepareUserRequest(t *testing.T, userForBody models.User, userNameForParam string) (*httptest.ResponseRecorder, *http.Request) {
+	bits, err := json.Marshal(userForBody)
+	assert.Nil(t, err)
+	body := bytes.NewReader(bits)
+	rec := httptest.NewRecorder()
+	req := httptest.NewRequest("ANY", "https://example.com", body) // only the body matters here
+	req = mux.SetURLVars(req, map[string]string{"username": userNameForParam})
+	return rec, req
+}
+
+func haveOnlyOneUser(t *testing.T, user models.User) {
+	deleteAllUsers(t)
+	var err error
+	if user.IsAdmin {
+		err = logic.CreateAdmin(&user)
+	} else {
+		err = logic.CreateUser(&user)
+	}
+	assert.Nil(t, err)
+}
+
+func assertUserNameButNoPassword(t *testing.T, r io.Reader, userName string) {
+	var resp models.User
+	err := json.NewDecoder(r).Decode(&resp)
+	assert.Nil(t, err)
+	assert.Equal(t, userName, resp.UserName)
+	assert.Empty(t, resp.Password)
+}
+
 func TestHasAdmin(t *testing.T) {
 func TestHasAdmin(t *testing.T) {
 	// delete all current users
 	// delete all current users
 	users, _ := logic.GetUsers()
 	users, _ := logic.GetUsers()

+ 0 - 6
docker/Caddyfile

@@ -26,12 +26,6 @@ https://api.{$NM_DOMAIN} {
 	reverse_proxy http://netmaker:8081
 	reverse_proxy http://netmaker:8081
 }
 }
 
 
-# STUN
-https://stun.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
-	reverse_proxy netmaker:3478
-}
-
 # TURN
 # TURN
 https://turn.{$NM_DOMAIN} {
 https://turn.{$NM_DOMAIN} {
 	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	tls /root/certs/fullchain.pem /root/certs/privkey.pem

+ 0 - 6
docker/Caddyfile-EE

@@ -44,12 +44,6 @@ https://api.{$NM_DOMAIN} {
 	reverse_proxy http://netmaker:8081
 	reverse_proxy http://netmaker:8081
 }
 }
 
 
-# STUN
-https://stun.{$NM_DOMAIN} {
-	tls /root/certs/fullchain.pem /root/certs/privkey.pem
-	reverse_proxy netmaker:3478
-}
-
 # TURN
 # TURN
 https://turn.{$NM_DOMAIN} {
 https://turn.{$NM_DOMAIN} {
 	tls /root/certs/fullchain.pem /root/certs/privkey.pem
 	tls /root/certs/fullchain.pem /root/certs/privkey.pem

+ 3 - 18
ee/initialize.go

@@ -16,6 +16,7 @@ import (
 // InitEE - Initialize EE Logic
 // InitEE - Initialize EE Logic
 func InitEE() {
 func InitEE() {
 	setIsEnterprise()
 	setIsEnterprise()
+	servercfg.Is_EE = true
 	models.SetLogo(retrieveEELogo())
 	models.SetLogo(retrieveEELogo())
 	controller.HttpHandlers = append(
 	controller.HttpHandlers = append(
 		controller.HttpHandlers,
 		controller.HttpHandlers,
@@ -27,13 +28,8 @@ func InitEE() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==
 		// == License Handling ==
 		ValidateLicense()
 		ValidateLicense()
-		if Limits.FreeTier {
-			logger.Log(0, "proceeding with Free Tier license")
-			logic.SetFreeTierForTelemetry(true)
-		} else {
-			logger.Log(0, "proceeding with Paid Tier license")
-			logic.SetFreeTierForTelemetry(false)
-		}
+		logger.Log(0, "proceeding with Paid Tier license")
+		logic.SetFreeTierForTelemetry(false)
 		// == End License Handling ==
 		// == End License Handling ==
 		AddLicenseHooks()
 		AddLicenseHooks()
 		resetFailover()
 		resetFailover()
@@ -46,17 +42,6 @@ func InitEE() {
 	logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
 	logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
 }
 }
 
 
-func setControllerLimits() {
-	logic.Node_Limit = Limits.Nodes
-	logic.Users_Limit = Limits.Users
-	logic.Clients_Limit = Limits.Clients
-	logic.Free_Tier = Limits.FreeTier
-	servercfg.Is_EE = true
-	if logic.Free_Tier {
-		logic.Networks_Limit = 3
-	}
-}
-
 func resetFailover() {
 func resetFailover() {
 	nets, err := logic.GetNetworks()
 	nets, err := logic.GetNetworks()
 	if err == nil {
 	if err == nil {

+ 13 - 19
ee/license.go

@@ -9,12 +9,13 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"math"
 	"net/http"
 	"net/http"
+	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/nacl/box"
 	"golang.org/x/crypto/nacl/box"
@@ -31,8 +32,14 @@ type apiServerConf struct {
 
 
 // AddLicenseHooks - adds the validation and cache clear hooks
 // AddLicenseHooks - adds the validation and cache clear hooks
 func AddLicenseHooks() {
 func AddLicenseHooks() {
-	logic.AddHook(ValidateLicense)
-	logic.AddHook(ClearLicenseCache)
+	logic.HookManagerCh <- models.HookDetails{
+		Hook:     ValidateLicense,
+		Interval: time.Hour,
+	}
+	logic.HookManagerCh <- models.HookDetails{
+		Hook:     ClearLicenseCache,
+		Interval: time.Hour,
+	}
 }
 }
 
 
 // ValidateLicense - the initial license check for netmaker server
 // ValidateLicense - the initial license check for netmaker server
@@ -58,8 +65,8 @@ func ValidateLicense() error {
 	}
 	}
 
 
 	licenseSecret := LicenseSecret{
 	licenseSecret := LicenseSecret{
-		UserID: netmakerAccountID,
-		Limits: getCurrentServerLimit(),
+		AssociatedID: netmakerAccountID,
+		Limits:       getCurrentServerLimit(),
 	}
 	}
 
 
 	secretData, err := json.Marshal(&licenseSecret)
 	secretData, err := json.Marshal(&licenseSecret)
@@ -92,17 +99,6 @@ func ValidateLicense() error {
 		logger.FatalLog0(errValidation.Error())
 		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
-	Limits.Networks = math.MaxInt
-	Limits.FreeTier = license.FreeTier == "yes"
-	Limits.Clients = license.LimitClients
-	Limits.Nodes = license.LimitNodes
-	Limits.Servers = license.LimitServers
-	Limits.Users = license.LimitUsers
-	if Limits.FreeTier {
-		Limits.Networks = 3
-	}
-	setControllerLimits()
-
 	logger.Log(0, "License validation succeeded!")
 	logger.Log(0, "License validation succeeded!")
 	return nil
 	return nil
 }
 }
@@ -167,6 +163,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
 	}
 	}
 
 
 	msg := ValidateLicenseRequest{
 	msg := ValidateLicenseRequest{
+		LicenseKey:     servercfg.GetLicenseKey(),
 		NmServerPubKey: base64encode(publicKeyBytes),
 		NmServerPubKey: base64encode(publicKeyBytes),
 		EncryptedPart:  base64encode(encryptedData),
 		EncryptedPart:  base64encode(encryptedData),
 	}
 	}
@@ -180,9 +177,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	reqParams := req.URL.Query()
-	reqParams.Add("licensevalue", servercfg.GetLicenseKey())
-	req.URL.RawQuery = reqParams.Encode()
 	req.Header.Add("Content-Type", "application/json")
 	req.Header.Add("Content-Type", "application/json")
 	req.Header.Add("Accept", "application/json")
 	req.Header.Add("Accept", "application/json")
 	client := &http.Client{}
 	client := &http.Client{}

+ 20 - 38
ee/types.go

@@ -3,7 +3,7 @@ package ee
 import "fmt"
 import "fmt"
 
 
 const (
 const (
-	api_endpoint               = "https://api.controller.netmaker.io/api/v1/license/validate"
+	api_endpoint               = "https://api.accounts.netmaker.io/api/v1/license/validate"
 	license_cache_key          = "license_response_cache"
 	license_cache_key          = "license_response_cache"
 	license_validation_err_msg = "invalid license"
 	license_validation_err_msg = "invalid license"
 	server_id_key              = "nm-server-id"
 	server_id_key              = "nm-server-id"
@@ -11,38 +11,17 @@ const (
 
 
 var errValidation = fmt.Errorf(license_validation_err_msg)
 var errValidation = fmt.Errorf(license_validation_err_msg)
 
 
-// Limits - limits to be referenced throughout server
-var Limits = GlobalLimits{
-	Servers:  0,
-	Users:    0,
-	Nodes:    0,
-	Clients:  0,
-	Networks: 0,
-	FreeTier: false,
-}
-
-// GlobalLimits - struct for holding global limits on this netmaker server in memory
-type GlobalLimits struct {
-	Servers  int
-	Users    int
-	Nodes    int
-	Clients  int
-	FreeTier bool
-	Networks int
-}
-
 // LicenseKey - the license key struct representation with associated data
 // LicenseKey - the license key struct representation with associated data
 type LicenseKey struct {
 type LicenseKey struct {
-	LicenseValue   string `json:"license_value"` // actual (public) key and the unique value for the key
-	Expiration     int64  `json:"expiration"`
-	LimitServers   int    `json:"limit_servers"`
-	LimitUsers     int    `json:"limit_users"`
-	LimitNodes     int    `json:"limit_nodes"`
-	LimitClients   int    `json:"limit_clients"`
-	Metadata       string `json:"metadata"`
-	SubscriptionID string `json:"subscription_id"` // for a paid subscription (non-free-tier license)
-	FreeTier       string `json:"free_tier"`       // yes if free tier
-	IsActive       string `json:"is_active"`       // yes if active
+	LicenseValue  string `json:"license_value"` // actual (public) key and the unique value for the key
+	Expiration    int64  `json:"expiration"`
+	LimitServers  int    `json:"limit_servers"`
+	LimitUsers    int    `json:"limit_users"`
+	LimitHosts    int    `json:"limit_hosts"`
+	LimitNetworks int    `json:"limit_networks"`
+	LimitClients  int    `json:"limit_clients"`
+	Metadata      string `json:"metadata"`
+	IsActive      bool   `json:"is_active"` // yes if active
 }
 }
 
 
 // ValidatedLicense - the validated license struct
 // ValidatedLicense - the validated license struct
@@ -53,28 +32,31 @@ type ValidatedLicense struct {
 
 
 // LicenseSecret - the encrypted struct for sending user-id
 // LicenseSecret - the encrypted struct for sending user-id
 type LicenseSecret struct {
 type LicenseSecret struct {
-	UserID string        `json:"user_id" binding:"required"` // UUID for user foreign key to User table
-	Limits LicenseLimits `json:"limits" binding:"required"`
+	AssociatedID string        `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
+	Limits       LicenseLimits `json:"limits" binding:"required"`
 }
 }
 
 
 // LicenseLimits - struct license limits
 // LicenseLimits - struct license limits
 type LicenseLimits struct {
 type LicenseLimits struct {
-	Servers int `json:"servers" binding:"required"`
-	Users   int `json:"users" binding:"required"`
-	Nodes   int `json:"nodes" binding:"required"`
-	Clients int `json:"clients" binding:"required"`
+	Servers  int `json:"servers"`
+	Users    int `json:"users"`
+	Hosts    int `json:"hosts"`
+	Clients  int `json:"clients"`
+	Networks int `json:"networks"`
 }
 }
 
 
 // LicenseLimits.SetDefaults - sets the default values for limits
 // LicenseLimits.SetDefaults - sets the default values for limits
 func (l *LicenseLimits) SetDefaults() {
 func (l *LicenseLimits) SetDefaults() {
 	l.Clients = 0
 	l.Clients = 0
 	l.Servers = 1
 	l.Servers = 1
-	l.Nodes = 0
+	l.Hosts = 0
 	l.Users = 1
 	l.Users = 1
+	l.Networks = 0
 }
 }
 
 
 // ValidateLicenseRequest - used for request to validate license endpoint
 // ValidateLicenseRequest - used for request to validate license endpoint
 type ValidateLicenseRequest struct {
 type ValidateLicenseRequest struct {
+	LicenseKey     string `json:"license_key" binding:"required"`
 	NmServerPubKey string `json:"nm_server_pub_key" binding:"required"` // Netmaker server public key used to send data back to Netmaker for the Netmaker server to decrypt (eg output from validating license)
 	NmServerPubKey string `json:"nm_server_pub_key" binding:"required"` // Netmaker server public key used to send data back to Netmaker for the Netmaker server to decrypt (eg output from validating license)
 	EncryptedPart  string `json:"secret" binding:"required"`
 	EncryptedPart  string `json:"secret" binding:"required"`
 }
 }

+ 6 - 3
ee/util.go

@@ -30,12 +30,11 @@ func base64decode(input string) []byte {
 
 
 	return bytes
 	return bytes
 }
 }
-
 func getCurrentServerLimit() (limits LicenseLimits) {
 func getCurrentServerLimit() (limits LicenseLimits) {
 	limits.SetDefaults()
 	limits.SetDefaults()
-	nodes, err := logic.GetAllNodes()
+	hosts, err := logic.GetAllHosts()
 	if err == nil {
 	if err == nil {
-		limits.Nodes = len(nodes)
+		limits.Hosts = len(hosts)
 	}
 	}
 	clients, err := logic.GetAllExtClients()
 	clients, err := logic.GetAllExtClients()
 	if err == nil {
 	if err == nil {
@@ -45,5 +44,9 @@ func getCurrentServerLimit() (limits LicenseLimits) {
 	if err == nil {
 	if err == nil {
 		limits.Users = len(users)
 		limits.Users = len(users)
 	}
 	}
+	networks, err := logic.GetNetworks()
+	if err == nil {
+		limits.Networks = len(networks)
+	}
 	return
 	return
 }
 }

+ 6 - 8
go.mod

@@ -15,11 +15,11 @@ require (
 	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.8.4
 	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.4.0
 	github.com/txn2/txeh v1.4.0
-	golang.org/x/crypto v0.9.0
-	golang.org/x/net v0.10.0 // indirect
-	golang.org/x/oauth2 v0.8.0
-	golang.org/x/sys v0.8.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/crypto v0.10.0
+	golang.org/x/net v0.11.0 // indirect
+	golang.org/x/oauth2 v0.9.0
+	golang.org/x/sys v0.9.0 // indirect
+	golang.org/x/text v0.10.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1
@@ -34,13 +34,12 @@ require (
 require (
 require (
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/gorilla/websocket v1.5.0
-	github.com/pkg/errors v0.9.1
 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
 	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
-	gortc.io/stun v1.23.0
 )
 )
 
 
 require (
 require (
 	github.com/devilcove/httpclient v0.6.0
 	github.com/devilcove/httpclient v0.6.0
+	github.com/go-jose/go-jose/v3 v3.0.0
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/matryer/is v1.4.1
 	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/olekukonko/tablewriter v0.0.5
@@ -50,7 +49,6 @@ require (
 require (
 require (
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
-	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect

+ 10 - 14
go.sum

@@ -77,8 +77,6 @@ github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5A
 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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
@@ -115,8 +113,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
-golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -127,10 +125,10 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
-golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
+golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -146,8 +144,8 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 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=
@@ -155,8 +153,8 @@ 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
+golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 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/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -177,5 +175,3 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=
-gortc.io/stun v1.23.0/go.mod h1:XD5lpONVyjvV3BgOyJFNo0iv6R2oZB4L+weMqxts+zg=

+ 1 - 1
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       hostNetwork: true
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.20.2
+        image: gravitl/netclient:v0.20.4
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/client/netclient.yaml

@@ -28,7 +28,7 @@ spec:
       #           - "<node label value>"
       #           - "<node label value>"
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.20.2
+        image: gravitl/netclient:v0.20.4
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,7 @@ spec:
     spec:
     spec:
       containers:
       containers:
       - name: netmaker-ui
       - name: netmaker-ui
-        image: gravitl/netmaker-ui:v0.20.2
+        image: gravitl/netmaker-ui:v0.20.4
         ports:
         ports:
         - containerPort: 443
         - containerPort: 443
         env:
         env:

+ 73 - 8
logic/acls/common.go

@@ -2,24 +2,57 @@ package acls
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"sync"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"golang.org/x/exp/slog"
 )
 )
 
 
+var (
+	aclCacheMutex = &sync.RWMutex{}
+	aclCacheMap   = make(map[ContainerID]ACLContainer)
+	aclMutex      = &sync.RWMutex{}
+)
+
+func fetchAclContainerFromCache(containerID ContainerID) (aclCont ACLContainer, ok bool) {
+	aclCacheMutex.RLock()
+	aclCont, ok = aclCacheMap[containerID]
+	aclCacheMutex.RUnlock()
+	return
+}
+
+func storeAclContainerInCache(containerID ContainerID, aclContainer ACLContainer) {
+	aclCacheMutex.Lock()
+	aclCacheMap[containerID] = aclContainer
+	aclCacheMutex.Unlock()
+}
+
+func DeleteAclFromCache(containerID ContainerID) {
+	aclCacheMutex.Lock()
+	delete(aclCacheMap, containerID)
+	aclCacheMutex.Unlock()
+}
+
 // == type functions ==
 // == type functions ==
 
 
 // ACL.Allow - allows access by ID in memory
 // ACL.Allow - allows access by ID in memory
 func (acl ACL) Allow(ID AclID) {
 func (acl ACL) Allow(ID AclID) {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	acl[ID] = Allowed
 	acl[ID] = Allowed
 }
 }
 
 
 // ACL.DisallowNode - disallows access by ID in memory
 // ACL.DisallowNode - disallows access by ID in memory
 func (acl ACL) Disallow(ID AclID) {
 func (acl ACL) Disallow(ID AclID) {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	acl[ID] = NotAllowed
 	acl[ID] = NotAllowed
 }
 }
 
 
 // ACL.Remove - removes a node from a ACL in memory
 // ACL.Remove - removes a node from a ACL in memory
 func (acl ACL) Remove(ID AclID) {
 func (acl ACL) Remove(ID AclID) {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	delete(acl, ID)
 	delete(acl, ID)
 }
 }
 
 
@@ -29,29 +62,47 @@ func (acl ACL) Save(containerID ContainerID, ID AclID) (ACL, error) {
 }
 }
 
 
 // ACL.IsAllowed - sees if ID is allowed in referring ACL
 // ACL.IsAllowed - sees if ID is allowed in referring ACL
-func (acl ACL) IsAllowed(ID AclID) bool {
-	return acl[ID] == Allowed
-}
-
-// ACLContainer.IsAllowed - returns if the current ACL container contains allowed ACLs between two IDs
-func (aclContainer ACLContainer) IsAllowed(ID1, ID2 AclID) bool {
-	return aclContainer[ID1].IsAllowed(ID2) && aclContainer[ID2].IsAllowed(ID1)
+func (acl ACL) IsAllowed(ID AclID) (allowed bool) {
+	aclMutex.RLock()
+	allowed = acl[ID] == Allowed
+	aclMutex.RUnlock()
+	return
 }
 }
 
 
 // ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
 // ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
 func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
 func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	aclContainer[ID] = acl
 	aclContainer[ID] = acl
 	return aclContainer
 	return aclContainer
 }
 }
 
 
 // ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
 // ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
 func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
 func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	delete(aclContainer, ID)
 	delete(aclContainer, ID)
 	return aclContainer
 	return aclContainer
 }
 }
 
 
 // ACLContainer.ChangeAccess - changes the relationship between two nodes in memory
 // ACLContainer.ChangeAccess - changes the relationship between two nodes in memory
 func (networkACL ACLContainer) ChangeAccess(ID1, ID2 AclID, value byte) {
 func (networkACL ACLContainer) ChangeAccess(ID1, ID2 AclID, value byte) {
+	if _, ok := networkACL[ID1]; !ok {
+		slog.Error("ACL missing for ", "id", ID1)
+		return
+	}
+	if _, ok := networkACL[ID2]; !ok {
+		slog.Error("ACL missing for ", "id", ID2)
+		return
+	}
+	if _, ok := networkACL[ID1][ID2]; !ok {
+		slog.Error("ACL missing for ", "id1", ID1, "id2", ID2)
+		return
+	}
+	if _, ok := networkACL[ID2][ID1]; !ok {
+		slog.Error("ACL missing for ", "id2", ID2, "id1", ID1)
+		return
+	}
 	networkACL[ID1][ID2] = value
 	networkACL[ID1][ID2] = value
 	networkACL[ID2][ID1] = value
 	networkACL[ID2][ID1] = value
 }
 }
@@ -75,6 +126,11 @@ func (aclContainer ACLContainer) Get(containerID ContainerID) (ACLContainer, err
 
 
 // fetchACLContainer - fetches all current rules in given ACL container
 // fetchACLContainer - fetches all current rules in given ACL container
 func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
 func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
+	aclMutex.RLock()
+	defer aclMutex.RUnlock()
+	if aclContainer, ok := fetchAclContainerFromCache(containerID); ok {
+		return aclContainer, nil
+	}
 	aclJson, err := fetchACLContainerJson(ContainerID(containerID))
 	aclJson, err := fetchACLContainerJson(ContainerID(containerID))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -83,6 +139,7 @@ func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
 	if err := json.Unmarshal([]byte(aclJson), &currentNetworkACL); err != nil {
 	if err := json.Unmarshal([]byte(aclJson), &currentNetworkACL); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	storeAclContainerInCache(containerID, currentNetworkACL)
 	return currentNetworkACL, nil
 	return currentNetworkACL, nil
 }
 }
 
 
@@ -109,10 +166,18 @@ func upsertACL(containerID ContainerID, ID AclID, acl ACL) (ACL, error) {
 // upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
 // upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
 // if nil, create it
 // if nil, create it
 func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
 func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
+	aclMutex.Lock()
+	defer aclMutex.Unlock()
 	if aclContainer == nil {
 	if aclContainer == nil {
 		aclContainer = make(ACLContainer)
 		aclContainer = make(ACLContainer)
 	}
 	}
-	return aclContainer, database.Insert(string(containerID), string(convertNetworkACLtoACLJson(aclContainer)), database.NODE_ACLS_TABLE_NAME)
+
+	err := database.Insert(string(containerID), string(convertNetworkACLtoACLJson(aclContainer)), database.NODE_ACLS_TABLE_NAME)
+	if err != nil {
+		return aclContainer, err
+	}
+	storeAclContainerInCache(containerID, aclContainer)
+	return aclContainer, nil
 }
 }
 
 
 func convertNetworkACLtoACLJson(networkACL ACLContainer) ACLJson {
 func convertNetworkACLtoACLJson(networkACL ACLContainer) ACLJson {

+ 6 - 1
logic/acls/nodeacls/modify.go

@@ -83,5 +83,10 @@ func RemoveNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACLContainer, error
 
 
 // DeleteACLContainer - removes an ACLContainer state from db
 // DeleteACLContainer - removes an ACLContainer state from db
 func DeleteACLContainer(network NetworkID) error {
 func DeleteACLContainer(network NetworkID) error {
-	return database.DeleteRecord(database.NODE_ACLS_TABLE_NAME, string(network))
+	err := database.DeleteRecord(database.NODE_ACLS_TABLE_NAME, string(network))
+	if err != nil {
+		return err
+	}
+	acls.DeleteAclFromCache(acls.ContainerID(network))
+	return nil
 }
 }

+ 0 - 14
logic/auth.go

@@ -42,20 +42,6 @@ func HasAdmin() (bool, error) {
 	return false, err
 	return false, err
 }
 }
 
 
-// GetReturnUser - gets a user
-func GetReturnUser(username string) (models.ReturnUser, error) {
-
-	var user models.ReturnUser
-	record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
-	if err != nil {
-		return user, err
-	}
-	if err = json.Unmarshal([]byte(record), &user); err != nil {
-		return models.ReturnUser{}, err
-	}
-	return user, err
-}
-
 // GetUsers - gets users
 // GetUsers - gets users
 func GetUsers() ([]models.ReturnUser, error) {
 func GetUsers() ([]models.ReturnUser, error) {
 
 

+ 2 - 6
logic/dns.go

@@ -69,16 +69,12 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 
 
 	var dns []models.DNSEntry
 	var dns []models.DNSEntry
 
 
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
+	nodes, err := GetNetworkNodes(network)
 	if err != nil {
 	if err != nil {
 		return dns, err
 		return dns, err
 	}
 	}
 
 
-	for _, value := range collection {
-		var node models.Node
-		if err = json.Unmarshal([]byte(value), &node); err != nil {
-			continue
-		}
+	for _, node := range nodes {
 		if node.Network != network {
 		if node.Network != network {
 			continue
 			continue
 		}
 		}

+ 53 - 35
logic/extpeers.go

@@ -3,58 +3,56 @@ package logic
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"sync"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
-// GetExtPeersList - gets the ext peers lists
-func GetExtPeersList(node *models.Node) ([]models.ExtPeersResponse, error) {
-
-	var peers []models.ExtPeersResponse
-	records, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
+var (
+	extClientCacheMutex = &sync.RWMutex{}
+	extClientCacheMap   = make(map[string]models.ExtClient)
+)
 
 
-	if err != nil {
-		return peers, err
+func getAllExtClientsFromCache() (extClients []models.ExtClient) {
+	extClientCacheMutex.RLock()
+	for _, extclient := range extClientCacheMap {
+		extClients = append(extClients, extclient)
 	}
 	}
+	extClientCacheMutex.RUnlock()
+	return
+}
 
 
-	for _, value := range records {
-		var peer models.ExtPeersResponse
-		var extClient models.ExtClient
-		err = json.Unmarshal([]byte(value), &peer)
-		if err != nil {
-			logger.Log(2, "failed to unmarshal peer when getting ext peer list")
-			continue
-		}
-		err = json.Unmarshal([]byte(value), &extClient)
-		if err != nil {
-			logger.Log(2, "failed to unmarshal ext client")
-			continue
-		}
+func deleteExtClientFromCache(key string) {
+	extClientCacheMutex.Lock()
+	delete(extClientCacheMap, key)
+	extClientCacheMutex.Unlock()
+}
 
 
-		if extClient.Enabled && extClient.Network == node.Network && extClient.IngressGatewayID == node.ID.String() {
-			peers = append(peers, peer)
-		}
-	}
-	return peers, err
+func getExtClientFromCache(key string) (extclient models.ExtClient, ok bool) {
+	extClientCacheMutex.RLock()
+	extclient, ok = extClientCacheMap[key]
+	extClientCacheMutex.RUnlock()
+	return
+}
+
+func storeExtClientInCache(key string, extclient models.ExtClient) {
+	extClientCacheMutex.Lock()
+	extClientCacheMap[key] = extclient
+	extClientCacheMutex.Unlock()
 }
 }
 
 
 // ExtClient.GetEgressRangesOnNetwork - returns the egress ranges on network of ext client
 // ExtClient.GetEgressRangesOnNetwork - returns the egress ranges on network of ext client
 func GetEgressRangesOnNetwork(client *models.ExtClient) ([]string, error) {
 func GetEgressRangesOnNetwork(client *models.ExtClient) ([]string, error) {
 
 
 	var result []string
 	var result []string
-	nodesData, err := database.FetchRecords(database.NODES_TABLE_NAME)
+	networkNodes, err := GetNetworkNodes(client.Network)
 	if err != nil {
 	if err != nil {
 		return []string{}, err
 		return []string{}, err
 	}
 	}
-	for _, nodeData := range nodesData {
-		var currentNode models.Node
-		if err = json.Unmarshal([]byte(nodeData), &currentNode); err != nil {
-			continue
-		}
+	for _, currentNode := range networkNodes {
 		if currentNode.Network != client.Network {
 		if currentNode.Network != client.Network {
 			continue
 			continue
 		}
 		}
@@ -75,13 +73,25 @@ func DeleteExtClient(network string, clientid string) error {
 		return err
 		return err
 	}
 	}
 	err = database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, key)
 	err = database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, key)
-	return err
+	if err != nil {
+		return err
+	}
+	deleteExtClientFromCache(key)
+	return nil
 }
 }
 
 
 // GetNetworkExtClients - gets the ext clients of given network
 // GetNetworkExtClients - gets the ext clients of given network
 func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
 func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
 	var extclients []models.ExtClient
 	var extclients []models.ExtClient
-
+	allextclients := getAllExtClientsFromCache()
+	if len(allextclients) != 0 {
+		for _, extclient := range allextclients {
+			if extclient.Network == network {
+				extclients = append(extclients, extclient)
+			}
+		}
+		return extclients, nil
+	}
 	records, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
 	records, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME)
 	if err != nil {
 	if err != nil {
 		return extclients, err
 		return extclients, err
@@ -92,6 +102,10 @@ func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
+		key, err := GetRecordKey(extclient.ClientID, network)
+		if err == nil {
+			storeExtClientInCache(key, extclient)
+		}
 		if extclient.Network == network {
 		if extclient.Network == network {
 			extclients = append(extclients, extclient)
 			extclients = append(extclients, extclient)
 		}
 		}
@@ -106,12 +120,15 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 	if err != nil {
 	if err != nil {
 		return extclient, err
 		return extclient, err
 	}
 	}
+	if extclient, ok := getExtClientFromCache(key); ok {
+		return extclient, nil
+	}
 	data, err := database.FetchRecord(database.EXT_CLIENT_TABLE_NAME, key)
 	data, err := database.FetchRecord(database.EXT_CLIENT_TABLE_NAME, key)
 	if err != nil {
 	if err != nil {
 		return extclient, err
 		return extclient, err
 	}
 	}
 	err = json.Unmarshal([]byte(data), &extclient)
 	err = json.Unmarshal([]byte(data), &extclient)
-
+	storeExtClientInCache(key, extclient)
 	return extclient, err
 	return extclient, err
 }
 }
 
 
@@ -190,6 +207,7 @@ func SaveExtClient(extclient *models.ExtClient) error {
 	if err = database.Insert(key, string(data), database.EXT_CLIENT_TABLE_NAME); err != nil {
 	if err = database.Insert(key, string(data), database.EXT_CLIENT_TABLE_NAME); err != nil {
 		return err
 		return err
 	}
 	}
+	storeExtClientInCache(key, *extclient)
 	return SetNetworkNodesLastModified(extclient.Network)
 	return SetNetworkNodesLastModified(extclient.Network)
 }
 }
 
 

+ 4 - 26
logic/gateway.go

@@ -1,7 +1,6 @@
 package logic
 package logic
 
 
 import (
 import (
-	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"time"
 	"time"
@@ -53,11 +52,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
 	node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
 	node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
 	node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
 	node.SetLastModified()
 	node.SetLastModified()
-	nodeData, err := json.Marshal(&node)
-	if err != nil {
-		return node, err
-	}
-	if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
+	if err = UpsertNode(&node); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	return node, nil
 	return node, nil
@@ -84,12 +79,7 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 	node.EgressGatewayRanges = []string{}
 	node.EgressGatewayRanges = []string{}
 	node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
 	node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
 	node.SetLastModified()
 	node.SetLastModified()
-
-	data, err := json.Marshal(&node)
-	if err != nil {
-		return models.Node{}, err
-	}
-	if err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
+	if err = UpsertNode(&node); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	return node, nil
 	return node, nil
@@ -115,9 +105,6 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if host.FirewallInUse == models.FIREWALL_NONE {
 	if host.FirewallInUse == models.FIREWALL_NONE {
 		return models.Node{}, errors.New("firewall is not supported for ingress gateways")
 		return models.Node{}, errors.New("firewall is not supported for ingress gateways")
 	}
 	}
-	if host.NatType != models.NAT_Types.Public {
-		return models.Node{}, errors.New("ingress cannot be created on nodes behind NAT")
-	}
 
 
 	network, err := GetParentNetwork(netid)
 	network, err := GetParentNetwork(netid)
 	if err != nil {
 	if err != nil {
@@ -131,11 +118,7 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if ingress.Failover && servercfg.Is_EE {
 	if ingress.Failover && servercfg.Is_EE {
 		node.Failover = true
 		node.Failover = true
 	}
 	}
-	data, err := json.Marshal(&node)
-	if err != nil {
-		return models.Node{}, err
-	}
-	err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
+	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
@@ -176,12 +159,7 @@ func DeleteIngressGateway(networkName string, nodeid string) (models.Node, bool,
 				node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
 				node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
 		}
 		}
 	}
 	}
-
-	data, err := json.Marshal(&node)
-	if err != nil {
-		return models.Node{}, false, removedClients, err
-	}
-	err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
+	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, wasFailover, removedClients, err
 		return models.Node{}, wasFailover, removedClients, err
 	}
 	}

+ 1 - 1
logic/host_test.go

@@ -42,7 +42,7 @@ func TestCheckPorts(t *testing.T) {
 	//not sure why this initialization is required but without it
 	//not sure why this initialization is required but without it
 	// RemoveHost returns database is closed
 	// RemoveHost returns database is closed
 	database.InitializeDatabase()
 	database.InitializeDatabase()
-	RemoveHost(&h)
+	RemoveHost(&h, true)
 	CreateHost(&h)
 	CreateHost(&h)
 	t.Run("no change", func(t *testing.T) {
 	t.Run("no change", func(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)

+ 114 - 18
logic/hosts.go

@@ -9,6 +9,7 @@ import (
 	"net/http"
 	"net/http"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
+	"sync"
 
 
 	"github.com/devilcove/httpclient"
 	"github.com/devilcove/httpclient"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -19,6 +20,11 @@ import (
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/crypto/bcrypt"
 )
 )
 
 
+var (
+	hostCacheMutex = &sync.RWMutex{}
+	hostsCacheMap  = make(map[string]models.Host)
+)
+
 var (
 var (
 	// ErrHostExists error indicating that host exists when trying to create new host
 	// ErrHostExists error indicating that host exists when trying to create new host
 	ErrHostExists error = errors.New("host already exists")
 	ErrHostExists error = errors.New("host already exists")
@@ -26,6 +32,46 @@ var (
 	ErrInvalidHostID error = errors.New("invalid host id")
 	ErrInvalidHostID error = errors.New("invalid host id")
 )
 )
 
 
+func getHostsFromCache() (hosts []models.Host) {
+	hostCacheMutex.RLock()
+	for _, host := range hostsCacheMap {
+		hosts = append(hosts, host)
+	}
+	hostCacheMutex.RUnlock()
+	return
+}
+
+func getHostsMapFromCache() (hostsMap map[string]models.Host) {
+	hostCacheMutex.RLock()
+	hostsMap = hostsCacheMap
+	hostCacheMutex.RUnlock()
+	return
+}
+
+func getHostFromCache(hostID string) (host models.Host, ok bool) {
+	hostCacheMutex.RLock()
+	host, ok = hostsCacheMap[hostID]
+	hostCacheMutex.RUnlock()
+	return
+}
+
+func storeHostInCache(h models.Host) {
+	hostCacheMutex.Lock()
+	hostsCacheMap[h.ID.String()] = h
+	hostCacheMutex.Unlock()
+}
+
+func deleteHostFromCache(hostID string) {
+	hostCacheMutex.Lock()
+	delete(hostsCacheMap, hostID)
+	hostCacheMutex.Unlock()
+}
+func loadHostsIntoCache(hMap map[string]models.Host) {
+	hostCacheMutex.Lock()
+	hostsCacheMap = hMap
+	hostCacheMutex.Unlock()
+}
+
 const (
 const (
 	maxPort = 1<<16 - 1
 	maxPort = 1<<16 - 1
 	minPort = 1025
 	minPort = 1025
@@ -33,17 +79,28 @@ const (
 
 
 // GetAllHosts - returns all hosts in flat list or error
 // GetAllHosts - returns all hosts in flat list or error
 func GetAllHosts() ([]models.Host, error) {
 func GetAllHosts() ([]models.Host, error) {
-	currHostMap, err := GetHostsMap()
-	if err != nil {
+
+	currHosts := getHostsFromCache()
+	if len(currHosts) != 0 {
+		return currHosts, nil
+	}
+	records, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
+	if err != nil && !database.IsEmptyRecord(err) {
 		return nil, err
 		return nil, err
 	}
 	}
-	var currentHosts = []models.Host{}
-	for k := range currHostMap {
-		var h = *currHostMap[k]
-		currentHosts = append(currentHosts, h)
+	currHostsMap := make(map[string]models.Host)
+	defer loadHostsIntoCache(currHostsMap)
+	for k := range records {
+		var h models.Host
+		err = json.Unmarshal([]byte(records[k]), &h)
+		if err != nil {
+			return nil, err
+		}
+		currHosts = append(currHosts, h)
+		currHostsMap[h.ID.String()] = h
 	}
 	}
 
 
-	return currentHosts, nil
+	return currHosts, nil
 }
 }
 
 
 // GetAllHostsAPI - get's all the hosts in an API usable format
 // GetAllHostsAPI - get's all the hosts in an API usable format
@@ -57,19 +114,24 @@ func GetAllHostsAPI(hosts []models.Host) []models.ApiHost {
 }
 }
 
 
 // GetHostsMap - gets all the current hosts on machine in a map
 // GetHostsMap - gets all the current hosts on machine in a map
-func GetHostsMap() (map[string]*models.Host, error) {
+func GetHostsMap() (map[string]models.Host, error) {
+	hostsMap := getHostsMapFromCache()
+	if len(hostsMap) != 0 {
+		return hostsMap, nil
+	}
 	records, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
 	records, err := database.FetchRecords(database.HOSTS_TABLE_NAME)
 	if err != nil && !database.IsEmptyRecord(err) {
 	if err != nil && !database.IsEmptyRecord(err) {
 		return nil, err
 		return nil, err
 	}
 	}
-	currHostMap := make(map[string]*models.Host)
+	currHostMap := make(map[string]models.Host)
+	defer loadHostsIntoCache(currHostMap)
 	for k := range records {
 	for k := range records {
 		var h models.Host
 		var h models.Host
 		err = json.Unmarshal([]byte(records[k]), &h)
 		err = json.Unmarshal([]byte(records[k]), &h)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		currHostMap[h.ID.String()] = &h
+		currHostMap[h.ID.String()] = h
 	}
 	}
 
 
 	return currHostMap, nil
 	return currHostMap, nil
@@ -77,6 +139,10 @@ func GetHostsMap() (map[string]*models.Host, error) {
 
 
 // GetHost - gets a host from db given id
 // GetHost - gets a host from db given id
 func GetHost(hostid string) (*models.Host, error) {
 func GetHost(hostid string) (*models.Host, error) {
+
+	if host, ok := getHostFromCache(hostid); ok {
+		return &host, nil
+	}
 	record, err := database.FetchRecord(database.HOSTS_TABLE_NAME, hostid)
 	record, err := database.FetchRecord(database.HOSTS_TABLE_NAME, hostid)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -86,13 +152,20 @@ func GetHost(hostid string) (*models.Host, error) {
 	if err = json.Unmarshal([]byte(record), &h); err != nil {
 	if err = json.Unmarshal([]byte(record), &h); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
+	storeHostInCache(h)
 	return &h, nil
 	return &h, nil
 }
 }
 
 
 // CreateHost - creates a host if not exist
 // CreateHost - creates a host if not exist
 func CreateHost(h *models.Host) error {
 func CreateHost(h *models.Host) error {
-	_, err := GetHost(h.ID.String())
+	hosts, err := GetAllHosts()
+	if err != nil && !database.IsEmptyRecord(err) {
+		return err
+	}
+	if len(hosts) >= Hosts_Limit {
+		return errors.New("free tier limits exceeded on hosts")
+	}
+	_, err = GetHost(h.ID.String())
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 		return ErrHostExists
 		return ErrHostExists
 	}
 	}
@@ -187,20 +260,37 @@ func UpsertHost(h *models.Host) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
-	return database.Insert(h.ID.String(), string(data), database.HOSTS_TABLE_NAME)
+	err = database.Insert(h.ID.String(), string(data), database.HOSTS_TABLE_NAME)
+	if err != nil {
+		return err
+	}
+	storeHostInCache(*h)
+	return nil
 }
 }
 
 
 // RemoveHost - removes a given host from server
 // RemoveHost - removes a given host from server
-func RemoveHost(h *models.Host) error {
-	if len(h.Nodes) > 0 {
+func RemoveHost(h *models.Host, forceDelete bool) error {
+	if !forceDelete && len(h.Nodes) > 0 {
 		return fmt.Errorf("host still has associated nodes")
 		return fmt.Errorf("host still has associated nodes")
 	}
 	}
+
 	if servercfg.IsUsingTurn() {
 	if servercfg.IsUsingTurn() {
 		DeRegisterHostWithTurn(h.ID.String())
 		DeRegisterHostWithTurn(h.ID.String())
 	}
 	}
 
 
-	return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
+	if len(h.Nodes) > 0 {
+		if err := DisassociateAllNodesFromHost(h.ID.String()); err != nil {
+			return err
+		}
+	}
+
+	err := database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
+	if err != nil {
+		return err
+	}
+
+	deleteHostFromCache(h.ID.String())
+	return nil
 }
 }
 
 
 // RemoveHostByID - removes a given host by id from server
 // RemoveHostByID - removes a given host by id from server
@@ -208,7 +298,13 @@ func RemoveHostByID(hostID string) error {
 	if servercfg.IsUsingTurn() {
 	if servercfg.IsUsingTurn() {
 		DeRegisterHostWithTurn(hostID)
 		DeRegisterHostWithTurn(hostID)
 	}
 	}
-	return database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
+
+	err := database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
+	if err != nil {
+		return err
+	}
+	deleteHostFromCache(hostID)
+	return nil
 }
 }
 
 
 // UpdateHostNetwork - adds/deletes host from a network
 // UpdateHostNetwork - adds/deletes host from a network

+ 17 - 179
logic/networks.go

@@ -115,24 +115,8 @@ func CreateNetwork(network models.Network) (models.Network, error) {
 
 
 // GetNetworkNonServerNodeCount - get number of network non server nodes
 // GetNetworkNonServerNodeCount - get number of network non server nodes
 func GetNetworkNonServerNodeCount(networkName string) (int, error) {
 func GetNetworkNonServerNodeCount(networkName string) (int, error) {
-
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	count := 0
-	if err != nil && !database.IsEmptyRecord(err) {
-		return count, err
-	}
-	for _, value := range collection {
-		var node models.Node
-		if err = json.Unmarshal([]byte(value), &node); err != nil {
-			return count, err
-		} else {
-			if node.Network == networkName {
-				count++
-			}
-		}
-	}
-
-	return count, nil
+	nodes, err := GetNetworkNodes(networkName)
+	return len(nodes), err
 }
 }
 
 
 // GetParentNetwork - get parent network
 // GetParentNetwork - get parent network
@@ -210,18 +194,12 @@ func UniqueAddress(networkName string, reverse bool) (net.IP, error) {
 func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 
 
 	isunique := true
 	isunique := true
-	collection, err := database.FetchRecords(tableName)
-	if err != nil {
-		return isunique
-	}
-
-	for _, value := range collection { // filter
-
-		if tableName == database.NODES_TABLE_NAME {
-			var node models.Node
-			if err = json.Unmarshal([]byte(value), &node); err != nil {
-				continue
-			}
+	if tableName == database.NODES_TABLE_NAME {
+		nodes, err := GetNetworkNodes(network)
+		if err != nil {
+			return isunique
+		}
+		for _, node := range nodes {
 			if isIpv6 {
 			if isIpv6 {
 				if node.Address6.IP.String() == ip && node.Network == network {
 				if node.Address6.IP.String() == ip && node.Network == network {
 					return false
 					return false
@@ -231,11 +209,15 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 					return false
 					return false
 				}
 				}
 			}
 			}
-		} else if tableName == database.EXT_CLIENT_TABLE_NAME {
-			var extClient models.ExtClient
-			if err = json.Unmarshal([]byte(value), &extClient); err != nil {
-				continue
-			}
+		}
+
+	} else if tableName == database.EXT_CLIENT_TABLE_NAME {
+
+		extClients, err := GetNetworkExtClients(network)
+		if err != nil {
+			return isunique
+		}
+		for _, extClient := range extClients { // filter
 			if isIpv6 {
 			if isIpv6 {
 				if (extClient.Address6 == ip) && extClient.Network == network {
 				if (extClient.Address6 == ip) && extClient.Network == network {
 					return false
 					return false
@@ -247,7 +229,6 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 				}
 				}
 			}
 			}
 		}
 		}
-
 	}
 	}
 
 
 	return isunique
 	return isunique
@@ -298,149 +279,6 @@ func UniqueAddress6(networkName string, reverse bool) (net.IP, error) {
 	return add, errors.New("ERROR: No unique IPv6 addresses available. Check network subnet")
 	return add, errors.New("ERROR: No unique IPv6 addresses available. Check network subnet")
 }
 }
 
 
-// UpdateNetworkLocalAddresses - updates network localaddresses
-func UpdateNetworkLocalAddresses(networkName string) error {
-
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
-
-	if err != nil {
-		return err
-	}
-
-	for _, value := range collection {
-
-		var node models.Node
-
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			fmt.Println("error in node address assignment!")
-			return err
-		}
-		if node.Network == networkName {
-			var ipaddr net.IP
-			var iperr error
-			ipaddr, iperr = UniqueAddress(networkName, false)
-			if iperr != nil {
-				fmt.Println("error in node  address assignment!")
-				return iperr
-			}
-
-			node.Address.IP = ipaddr
-			newNodeData, err := json.Marshal(&node)
-			if err != nil {
-				logger.Log(1, "error in node  address assignment!")
-				return err
-			}
-			database.Insert(node.ID.String(), string(newNodeData), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
-// RemoveNetworkNodeIPv6Addresses - removes network node IPv6 addresses
-func RemoveNetworkNodeIPv6Addresses(networkName string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		return err
-	}
-
-	for _, value := range collections {
-
-		var node models.Node
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			fmt.Println("error in node address assignment!")
-			return err
-		}
-		if node.Network == networkName {
-			node.Address6.IP = nil
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
-// UpdateNetworkNodeAddresses - updates network node addresses
-func UpdateNetworkNodeAddresses(networkName string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		return err
-	}
-
-	for _, value := range collections {
-
-		var node models.Node
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			logger.Log(1, "error in node ipv4 address assignment!")
-			return err
-		}
-		if node.Network == networkName {
-			var ipaddr net.IP
-			var iperr error
-			ipaddr, iperr = UniqueAddress(networkName, false)
-			if iperr != nil {
-				logger.Log(1, "error in node ipv4 address assignment!")
-				return iperr
-			}
-
-			node.Address.IP = ipaddr
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
-// UpdateNetworkNodeAddresses6 - updates network node addresses
-func UpdateNetworkNodeAddresses6(networkName string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		return err
-	}
-
-	for _, value := range collections {
-
-		var node models.Node
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			logger.Log(1, "error in node ipv6 address assignment!")
-			return err
-		}
-		if node.Network == networkName {
-			var ipaddr net.IP
-			var iperr error
-			ipaddr, iperr = UniqueAddress6(networkName, false)
-			if iperr != nil {
-				logger.Log(1, "error in node ipv6 address assignment!")
-				return iperr
-			}
-
-			node.Address6.IP = ipaddr
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
-		}
-	}
-
-	return nil
-}
-
 // IsNetworkNameUnique - checks to see if any other networks have the same name (id)
 // IsNetworkNameUnique - checks to see if any other networks have the same name (id)
 func IsNetworkNameUnique(network *models.Network) (bool, error) {
 func IsNetworkNameUnique(network *models.Network) (bool, error) {
 
 

+ 75 - 45
logic/nodes.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"sort"
 	"sort"
+	"sync"
 	"time"
 	"time"
 
 
 	validator "github.com/go-playground/validator/v10"
 	validator "github.com/go-playground/validator/v10"
@@ -17,11 +18,53 @@ import (
 	"github.com/gravitl/netmaker/logic/pro"
 	"github.com/gravitl/netmaker/logic/pro"
 	"github.com/gravitl/netmaker/logic/pro/proacls"
 	"github.com/gravitl/netmaker/logic/pro/proacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/validation"
 	"github.com/gravitl/netmaker/validation"
 )
 )
 
 
+var (
+	nodeCacheMutex = &sync.RWMutex{}
+	nodesCacheMap  = make(map[string]models.Node)
+)
+
+func getNodeFromCache(nodeID string) (node models.Node, ok bool) {
+	nodeCacheMutex.RLock()
+	node, ok = nodesCacheMap[nodeID]
+	nodeCacheMutex.RUnlock()
+	return
+}
+func getNodesFromCache() (nodes []models.Node) {
+	nodeCacheMutex.RLock()
+	for _, node := range nodesCacheMap {
+		nodes = append(nodes, node)
+	}
+	nodeCacheMutex.RUnlock()
+	return
+}
+
+func deleteNodeFromCache(nodeID string) {
+	nodeCacheMutex.Lock()
+	delete(nodesCacheMap, nodeID)
+	nodeCacheMutex.Unlock()
+}
+
+func storeNodeInCache(node models.Node) {
+	nodeCacheMutex.Lock()
+	nodesCacheMap[node.ID.String()] = node
+	nodeCacheMutex.Unlock()
+}
+
+func loadNodesIntoCache(nMap map[string]models.Node) {
+	nodeCacheMutex.Lock()
+	nodesCacheMap = nMap
+	nodeCacheMutex.Unlock()
+}
+func ClearNodeCache() {
+	nodeCacheMutex.Lock()
+	nodesCacheMap = make(map[string]models.Node)
+	nodeCacheMutex.Unlock()
+}
+
 const (
 const (
 	// RELAY_NODE_ERR - error to return if relay node is unfound
 	// RELAY_NODE_ERR - error to return if relay node is unfound
 	RELAY_NODE_ERR = "could not find relay for node"
 	RELAY_NODE_ERR = "could not find relay for node"
@@ -92,7 +135,12 @@ func UpdateNodeCheckin(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
+	err = database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
+	if err != nil {
+		return err
+	}
+	storeNodeInCache(*node)
+	return nil
 }
 }
 
 
 // UpsertNode - updates node in the DB
 // UpsertNode - updates node in the DB
@@ -102,7 +150,12 @@ func UpsertNode(newNode *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
+	err = database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
+	if err != nil {
+		return err
+	}
+	storeNodeInCache(*newNode)
+	return nil
 }
 }
 
 
 // UpdateNode - takes a node and updates another node with it's values
 // UpdateNode - takes a node and updates another node with it's values
@@ -134,7 +187,12 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 		if data, err := json.Marshal(newNode); err != nil {
 		if data, err := json.Marshal(newNode); err != nil {
 			return err
 			return err
 		} else {
 		} else {
-			return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
+			err = database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
+			if err != nil {
+				return err
+			}
+			storeNodeInCache(*newNode)
+			return nil
 		}
 		}
 	}
 	}
 
 
@@ -192,6 +250,7 @@ func deleteNodeByID(node *models.Node) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
+	deleteNodeFromCache(node.ID.String())
 	if servercfg.IsDNSMode() {
 	if servercfg.IsDNSMode() {
 		SetDNS()
 		SetDNS()
 	}
 	}
@@ -257,7 +316,12 @@ func IsFailoverPresent(network string) bool {
 // GetAllNodes - returns all nodes in the DB
 // GetAllNodes - returns all nodes in the DB
 func GetAllNodes() ([]models.Node, error) {
 func GetAllNodes() ([]models.Node, error) {
 	var nodes []models.Node
 	var nodes []models.Node
-
+	nodes = getNodesFromCache()
+	if len(nodes) != 0 {
+		return nodes, nil
+	}
+	nodesMap := make(map[string]models.Node)
+	defer loadNodesIntoCache(nodesMap)
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) {
 		if database.IsEmptyRecord(err) {
@@ -275,6 +339,7 @@ func GetAllNodes() ([]models.Node, error) {
 		}
 		}
 		// add node to our array
 		// add node to our array
 		nodes = append(nodes, node)
 		nodes = append(nodes, node)
+		nodesMap[node.ID.String()] = node
 	}
 	}
 
 
 	return nodes, nil
 	return nodes, nil
@@ -329,46 +394,10 @@ func GetRecordKey(id string, network string) (string, error) {
 	return id + "###" + network, nil
 	return id + "###" + network, nil
 }
 }
 
 
-// GetNodesByAddress - gets a node by mac address
-func GetNodesByAddress(network string, addresses []string) ([]models.Node, error) {
-	var nodes []models.Node
-	allnodes, err := GetAllNodes()
-	if err != nil {
-		return []models.Node{}, err
-	}
-	for _, node := range allnodes {
-		if node.Network == network && ncutils.StringSliceContains(addresses, node.Address.String()) {
-			nodes = append(nodes, node)
-		}
-	}
-	return nodes, nil
-}
-
-// GetDeletedNodeByMacAddress - get a deleted node
-func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, error) {
-
-	var node models.Node
-
-	key, err := GetRecordKey(macaddress, network)
-	if err != nil {
-		return node, err
-	}
-
-	record, err := database.FetchRecord(database.DELETED_NODES_TABLE_NAME, key)
-	if err != nil {
-		return models.Node{}, err
-	}
-
-	if err = json.Unmarshal([]byte(record), &node); err != nil {
-		return models.Node{}, err
-	}
-
-	SetNodeDefaults(&node)
-
-	return node, nil
-}
-
 func GetNodeByID(uuid string) (models.Node, error) {
 func GetNodeByID(uuid string) (models.Node, error) {
+	if node, ok := getNodeFromCache(uuid); ok {
+		return node, nil
+	}
 	var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
 	var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
@@ -377,6 +406,7 @@ func GetNodeByID(uuid string) (models.Node, error) {
 	if err = json.Unmarshal([]byte(record), &node); err != nil {
 	if err = json.Unmarshal([]byte(record), &node); err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
+	storeNodeInCache(node)
 	return node, nil
 	return node, nil
 }
 }
 
 
@@ -526,7 +556,7 @@ func createNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
+	storeNodeInCache(*node)
 	_, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), defaultACLVal)
 	_, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), defaultACLVal)
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "failed to create node ACL for node,", node.ID.String(), "err:", err.Error())
 		logger.Log(1, "failed to create node ACL for node,", node.ID.String(), "err:", err.Error())

+ 1 - 5
logic/peers.go

@@ -92,14 +92,10 @@ func NodePeersInfo(client *models.Client) (models.NodePeersInfo, error) {
 }
 }
 
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(host *models.Host, allNodes []models.Node) (models.HostPeerUpdate, error) {
 	if host == nil {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
 	}
-	allNodes, err := GetAllNodes()
-	if err != nil {
-		return models.HostPeerUpdate{}, err
-	}
 	// track which nodes are deleted
 	// track which nodes are deleted
 	// after peer calculation, if peer not in list, add delete config of peer
 	// after peer calculation, if peer not in list, add delete config of peer
 	hostPeerUpdate := models.HostPeerUpdate{
 	hostPeerUpdate := models.HostPeerUpdate{

+ 5 - 46
logic/relay.go

@@ -1,12 +1,10 @@
 package logic
 package logic
 
 
 import (
 import (
-	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 
 
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -33,26 +31,12 @@ func CreateRelay(relay models.RelayRequest) ([]models.Client, models.Node, error
 	node.IsRelay = true
 	node.IsRelay = true
 	node.RelayedNodes = relay.RelayedNodes
 	node.RelayedNodes = relay.RelayedNodes
 	node.SetLastModified()
 	node.SetLastModified()
-	nodeData, err := json.Marshal(&node)
+	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return relayedClients, node, err
 		return relayedClients, node, err
 	}
 	}
-	if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
-		return relayedClients, models.Node{}, err
-	}
-	relayedClients = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
-	for _, relayed := range relayedClients {
-		data, err := json.Marshal(&relayed.Node)
-		if err != nil {
-			logger.Log(0, "marshalling relayed node", err.Error())
-			continue
-		}
-		if err := database.Insert(relayed.Node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
-			logger.Log(0, "inserting relayed node", err.Error())
-			continue
-		}
-	}
-	return relayedClients, node, nil
+	returnnodes := SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
+	return returnnodes, node, nil
 }
 }
 
 
 // SetRelayedNodes- sets and saves node as relayed
 // SetRelayedNodes- sets and saves node as relayed
@@ -71,12 +55,7 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.C
 			node.RelayedBy = ""
 			node.RelayedBy = ""
 		}
 		}
 		node.SetLastModified()
 		node.SetLastModified()
-		data, err := json.Marshal(&node)
-		if err != nil {
-			logger.Log(0, "setRelayedNodes.Marshal", err.Error())
-			continue
-		}
-		if err := database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
+		if err := UpsertNode(&node); err != nil {
 			logger.Log(0, "setRelayedNodes.Insert", err.Error())
 			logger.Log(0, "setRelayedNodes.Insert", err.Error())
 			continue
 			continue
 		}
 		}
@@ -91,22 +70,6 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.C
 	return returnnodes
 	return returnnodes
 }
 }
 
 
-//func GetRelayedNodes(relayNode *models.Node) (models.Node, error) {
-//	var returnnodes []models.Node
-//	networkNodes, err := GetNetworkNodes(relayNode.Network)
-//	if err != nil {
-//		return returnnodes, err
-//	}
-//	for _, node := range networkNodes {
-//		for _, addr := range relayNode.RelayAddrs {
-//			if addr == node.Address.IP.String() || addr == node.Address6.IP.String() {
-//				returnnodes = append(returnnodes, node)
-//			}
-//		}
-//	}
-//	return returnnodes, nil
-//}
-
 // ValidateRelay - checks if relay is valid
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
 	var err error
@@ -152,11 +115,7 @@ func DeleteRelay(network, nodeid string) ([]models.Client, models.Node, error) {
 	node.IsRelay = false
 	node.IsRelay = false
 	node.RelayedNodes = []string{}
 	node.RelayedNodes = []string{}
 	node.SetLastModified()
 	node.SetLastModified()
-	data, err := json.Marshal(&node)
-	if err != nil {
-		return returnClients, models.Node{}, err
-	}
-	if err = database.Insert(nodeid, string(data), database.NODES_TABLE_NAME); err != nil {
+	if err = UpsertNode(&node); err != nil {
 		return returnClients, models.Node{}, err
 		return returnClients, models.Node{}, err
 	}
 	}
 	return returnClients, node, nil
 	return returnClients, node, nil

+ 11 - 2
logic/serverconf.go

@@ -4,17 +4,18 @@ import (
 	"encoding/json"
 	"encoding/json"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 var (
 var (
-	// Node_Limit - dummy var for community
-	Node_Limit = 1000000000
 	// Networks_Limit - dummy var for community
 	// Networks_Limit - dummy var for community
 	Networks_Limit = 1000000000
 	Networks_Limit = 1000000000
 	// Users_Limit - dummy var for community
 	// Users_Limit - dummy var for community
 	Users_Limit = 1000000000
 	Users_Limit = 1000000000
 	// Clients_Limit - dummy var for community
 	// Clients_Limit - dummy var for community
 	Clients_Limit = 1000000000
 	Clients_Limit = 1000000000
+	// Hosts_Limit - dummy var for community
+	Hosts_Limit = 1000000000
 	// Free_Tier - specifies if free tier
 	// Free_Tier - specifies if free tier
 	Free_Tier = false
 	Free_Tier = false
 )
 )
@@ -85,3 +86,11 @@ func StoreJWTSecret(privateKey string) error {
 	}
 	}
 	return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
 	return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
 }
 }
+
+func SetFreeTierLimits() {
+	Free_Tier = true
+	Users_Limit = servercfg.GetUserLimit()
+	Clients_Limit = servercfg.GetClientLimit()
+	Networks_Limit = servercfg.GetNetworkLimit()
+	Hosts_Limit = servercfg.GetHostLimit()
+}

+ 3 - 0
logic/telemetry.go

@@ -60,6 +60,7 @@ func sendTelemetry() error {
 		Event:      "daily checkin",
 		Event:      "daily checkin",
 		Properties: posthog.NewProperties().
 		Properties: posthog.NewProperties().
 			Set("nodes", d.Nodes).
 			Set("nodes", d.Nodes).
+			Set("hosts", d.Hosts).
 			Set("servers", d.Servers).
 			Set("servers", d.Servers).
 			Set("non-server nodes", d.Count.NonServer).
 			Set("non-server nodes", d.Count.NonServer).
 			Set("extclients", d.ExtClients).
 			Set("extclients", d.ExtClients).
@@ -84,6 +85,7 @@ func fetchTelemetryData() (telemetryData, error) {
 	data.ExtClients = getDBLength(database.EXT_CLIENT_TABLE_NAME)
 	data.ExtClients = getDBLength(database.EXT_CLIENT_TABLE_NAME)
 	data.Users = getDBLength(database.USERS_TABLE_NAME)
 	data.Users = getDBLength(database.USERS_TABLE_NAME)
 	data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
 	data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
+	data.Hosts = getDBLength(database.HOSTS_TABLE_NAME)
 	data.Version = servercfg.GetVersion()
 	data.Version = servercfg.GetVersion()
 	data.Servers = getServerCount()
 	data.Servers = getServerCount()
 	nodes, err := GetAllNodes()
 	nodes, err := GetAllNodes()
@@ -167,6 +169,7 @@ func getDBLength(dbname string) int {
 // telemetryData - What data to send to posthog
 // telemetryData - What data to send to posthog
 type telemetryData struct {
 type telemetryData struct {
 	Nodes      int
 	Nodes      int
+	Hosts      int
 	ExtClients int
 	ExtClients int
 	Users      int
 	Users      int
 	Count      clientCount
 	Count      clientCount

+ 36 - 0
logic/timer.go

@@ -1,10 +1,13 @@
 package logic
 package logic
 
 
 import (
 import (
+	"context"
 	"fmt"
 	"fmt"
+	"sync"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/models"
 )
 )
 
 
 // == Constants ==
 // == Constants ==
@@ -12,6 +15,9 @@ import (
 // How long to wait before sending telemetry to server (24 hours)
 // How long to wait before sending telemetry to server (24 hours)
 const timer_hours_between_runs = 24
 const timer_hours_between_runs = 24
 
 
+// HookManagerCh - channel to add any new hooks
+var HookManagerCh = make(chan models.HookDetails, 2)
+
 // == Public ==
 // == Public ==
 
 
 // TimerCheckpoint - Checks if 24 hours has passed since telemetry was last sent. If so, sends telemetry data to posthog
 // TimerCheckpoint - Checks if 24 hours has passed since telemetry was last sent. If so, sends telemetry data to posthog
@@ -40,6 +46,36 @@ func AddHook(ifaceToAdd interface{}) {
 	timeHooks = append(timeHooks, ifaceToAdd)
 	timeHooks = append(timeHooks, ifaceToAdd)
 }
 }
 
 
+// StartHookManager - listens on `HookManagerCh` to run any hook
+func StartHookManager(ctx context.Context, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for {
+		select {
+		case <-ctx.Done():
+			logger.Log(0, "## Stopping Hook Manager")
+			return
+		case newhook := <-HookManagerCh:
+			wg.Add(1)
+			go addHookWithInterval(ctx, wg, newhook.Hook, newhook.Interval)
+		}
+	}
+}
+
+func addHookWithInterval(ctx context.Context, wg *sync.WaitGroup, hook func() error, interval time.Duration) {
+	defer wg.Done()
+	ticker := time.NewTicker(interval)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker.C:
+			hook()
+		}
+	}
+
+}
+
 // == private ==
 // == private ==
 
 
 // timeHooks - functions to run once a day, functions must take no parameters
 // timeHooks - functions to run once a day, functions must take no parameters

+ 24 - 0
logic/users.go

@@ -26,6 +26,30 @@ func GetUser(username string) (*models.User, error) {
 	return &user, err
 	return &user, err
 }
 }
 
 
+// GetReturnUser - gets a user
+func GetReturnUser(username string) (models.ReturnUser, error) {
+
+	var user models.ReturnUser
+	record, err := database.FetchRecord(database.USERS_TABLE_NAME, username)
+	if err != nil {
+		return user, err
+	}
+	if err = json.Unmarshal([]byte(record), &user); err != nil {
+		return models.ReturnUser{}, err
+	}
+	return user, err
+}
+
+// ToReturnUser - gets a user as a return user
+func ToReturnUser(user models.User) models.ReturnUser {
+	return models.ReturnUser{
+		UserName: user.UserName,
+		Networks: user.Networks,
+		IsAdmin:  user.IsAdmin,
+		Groups:   user.Groups,
+	}
+}
+
 // GetGroupUsers - gets users in a group
 // GetGroupUsers - gets users in a group
 func GetGroupUsers(group string) ([]models.ReturnUser, error) {
 func GetGroupUsers(group string) ([]models.ReturnUser, error) {
 	var returnUsers []models.ReturnUser
 	var returnUsers []models.ReturnUser

+ 1 - 1
logic/zombie.go

@@ -120,7 +120,7 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 						continue
 						continue
 					}
 					}
 					if len(host.Nodes) == 0 {
 					if len(host.Nodes) == 0 {
-						if err := RemoveHost(host); err != nil {
+						if err := RemoveHost(host, true); err != nil {
 							logger.Log(0, "error deleting zombie host", host.ID.String(), err.Error())
 							logger.Log(0, "error deleting zombie host", host.ID.String(), err.Error())
 						}
 						}
 					}
 					}

+ 5 - 5
main.go

@@ -26,11 +26,10 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	"github.com/gravitl/netmaker/serverctl"
-	stunserver "github.com/gravitl/netmaker/stun-server"
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
-var version = "v0.20.2"
+var version = "v0.20.4"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {
@@ -42,6 +41,9 @@ func main() {
 	initialize()                       // initial db and acls
 	initialize()                       // initial db and acls
 	setGarbageCollection()
 	setGarbageCollection()
 	setVerbosity()
 	setVerbosity()
+	if servercfg.DeployedByOperator() && !servercfg.Is_EE {
+		logic.SetFreeTierLimits()
+	}
 	defer database.CloseDB()
 	defer database.CloseDB()
 	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
 	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
 	defer stop()
 	defer stop()
@@ -89,7 +91,6 @@ func initialize() { // Client Mode Prereq Check
 	if err != nil {
 	if err != nil {
 		logger.Log(1, "Timer error occurred: ", err.Error())
 		logger.Log(1, "Timer error occurred: ", err.Error())
 	}
 	}
-
 	logic.EnterpriseCheck()
 	logic.EnterpriseCheck()
 
 
 	var authProvider = auth.InitializeAuthProvider()
 	var authProvider = auth.InitializeAuthProvider()
@@ -138,9 +139,8 @@ func startControllers(wg *sync.WaitGroup, ctx context.Context) {
 	//Run MessageQueue
 	//Run MessageQueue
 	wg.Add(1)
 	wg.Add(1)
 	go runMessageQueue(wg, ctx)
 	go runMessageQueue(wg, ctx)
-	// starts the stun server
 	wg.Add(1)
 	wg.Add(1)
-	go stunserver.Start(wg, ctx)
+	go logic.StartHookManager(ctx, wg)
 }
 }
 
 
 // Should we be using a context vice a waitgroup????????????
 // Should we be using a context vice a waitgroup????????????

+ 2 - 0
models/api_host.go

@@ -34,6 +34,7 @@ type ApiHost struct {
 	RelayedBy          string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
 	RelayedBy          string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
 	IsRelay            bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
 	IsRelay            bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
 	RelayedHosts       []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
 	RelayedHosts       []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
+	NatType            string   `json:"nat_type" yaml:"nat_type"`
 }
 }
 
 
 // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
 // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@@ -64,6 +65,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.Verbosity = h.Verbosity
 	a.Verbosity = h.Verbosity
 	a.Version = h.Version
 	a.Version = h.Version
 	a.IsDefault = h.IsDefault
 	a.IsDefault = h.IsDefault
+	a.NatType = h.NatType
 	return &a
 	return &a
 }
 }
 
 

+ 7 - 5
models/mqtt.go

@@ -13,11 +13,13 @@ type NodePeersInfo struct {
 
 
 // HostPeerUpdate - struct for host peer updates
 // HostPeerUpdate - struct for host peer updates
 type HostPeerUpdate struct {
 type HostPeerUpdate struct {
-	Host            Host                 `json:"host" bson:"host" yaml:"host"`
-	Server          string               `json:"server" bson:"server" yaml:"server"`
-	ServerVersion   string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	Peers           []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
-	HostNetworkInfo HostInfoMap          `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
+	Host              Host                 `json:"host" bson:"host" yaml:"host"`
+	NodeAddrs         []net.IPNet          `json:"nodes_addrs" yaml:"nodes_addrs"`
+	Server            string               `json:"server" bson:"server" yaml:"server"`
+	ServerVersion     string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	Peers             []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
+	HostNetworkInfo   HostInfoMap          `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
+	EndpointDetection bool                 `json:"endpointdetection" yaml:"endpointdetection"`
 }
 }
 
 
 // IngressInfo - struct for ingress info
 // IngressInfo - struct for ingress info

+ 8 - 0
models/node.go

@@ -181,6 +181,14 @@ func isLess(ipA string, ipB string) bool {
 	return bytes.Compare(ipNetA, ipNetB) < 0
 	return bytes.Compare(ipNetA, ipNetB) < 0
 }
 }
 
 
+// Node.PrimaryAddress - return ipv4 address if present, else return ipv6
+func (node *Node) PrimaryAddressIPNet() net.IPNet {
+	if node.Address.IP != nil {
+		return node.Address
+	}
+	return node.Address6
+}
+
 // Node.PrimaryAddress - return ipv4 address if present, else return ipv6
 // Node.PrimaryAddress - return ipv4 address if present, else return ipv6
 func (node *Node) PrimaryAddress() string {
 func (node *Node) PrimaryAddress() string {
 	if node.Address.IP != nil {
 	if node.Address.IP != nil {

+ 16 - 0
models/structs.go

@@ -2,6 +2,7 @@ package models
 
 
 import (
 import (
 	"strings"
 	"strings"
+	"time"
 
 
 	jwt "github.com/golang-jwt/jwt/v4"
 	jwt "github.com/golang-jwt/jwt/v4"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -275,3 +276,18 @@ type StunServer struct {
 	Domain string `json:"domain" yaml:"domain"`
 	Domain string `json:"domain" yaml:"domain"`
 	Port   int    `json:"port" yaml:"port"`
 	Port   int    `json:"port" yaml:"port"`
 }
 }
+
+// HookDetails - struct to hold hook info
+type HookDetails struct {
+	Hook     func() error
+	Interval time.Duration
+}
+
+// LicenseLimits - struct license limits
+type LicenseLimits struct {
+	Servers  int `json:"servers"`
+	Users    int `json:"users"`
+	Hosts    int `json:"hosts"`
+	Clients  int `json:"clients"`
+	Networks int `json:"networks"`
+}

+ 6 - 1
mq/emqx.go

@@ -6,11 +6,14 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
+	"strings"
 	"sync"
 	"sync"
 
 
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
+const already_exists = "ALREADY_EXISTS"
+
 type (
 type (
 	emqxUser struct {
 	emqxUser struct {
 		UserID   string `json:"user_id"`
 		UserID   string `json:"user_id"`
@@ -99,7 +102,9 @@ func CreateEmqxUser(username, password string, admin bool) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		return fmt.Errorf("error creating EMQX user %v", string(msg))
+		if !strings.Contains(string(msg), already_exists) {
+			return fmt.Errorf("error creating EMQX user %v", string(msg))
+		}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 24 - 3
mq/handlers.go

@@ -225,7 +225,7 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 			return
 			return
 		}
 		}
 
 
-		_ = updateNodeMetrics(&currentNode, &newMetrics)
+		shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
 
 
 		if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
 		if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
 			slog.Error("failed to update node metrics", "id", id, "error", err)
 			slog.Error("failed to update node metrics", "id", id, "error", err)
@@ -243,7 +243,21 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 				slog.Error("failed to failover for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
 				slog.Error("failed to failover for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
 			}
 			}
 		}
 		}
-		slog.Info("updated node metrics", "id", id)
+
+		if shouldUpdate {
+			slog.Info("updating peers after node detected connectivity issues", "id", currentNode.ID, "network", currentNode.Network)
+			host, err := logic.GetHost(currentNode.HostID.String())
+			if err == nil {
+				nodes, err := logic.GetAllNodes()
+				if err != nil {
+					return
+				}
+				if err = PublishHostPeerUpdate(host, nodes); err != nil {
+					slog.Warn("failed to publish update after failover peer change for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
+				}
+			}
+		}
+		slog.Debug("updated node metrics", "id", id)
 	}
 	}
 }
 }
 
 
@@ -408,12 +422,19 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
-		h.DefaultInterface != currentHost.DefaultInterface
+		h.DefaultInterface != currentHost.DefaultInterface ||
+		(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) || (h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort)
 	if ifaceDelta { // only save if something changes
 	if ifaceDelta { // only save if something changes
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.Interfaces = h.Interfaces
 		currentHost.Interfaces = h.Interfaces
 		currentHost.DefaultInterface = h.DefaultInterface
 		currentHost.DefaultInterface = h.DefaultInterface
 		currentHost.NatType = h.NatType
 		currentHost.NatType = h.NatType
+		if h.ListenPort != 0 {
+			currentHost.ListenPort = h.ListenPort
+		}
+		if h.WgPublicListenPort != 0 {
+			currentHost.WgPublicListenPort = h.WgPublicListenPort
+		}
 		if err := logic.UpsertHost(currentHost); err != nil {
 		if err := logic.UpsertHost(currentHost); err != nil {
 			slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
 			slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
 			return false
 			return false

+ 1 - 4
mq/mq.go

@@ -79,10 +79,7 @@ func SetupMQTT() {
 		}
 		}
 
 
 		opts.SetOrderMatters(false)
 		opts.SetOrderMatters(false)
-		opts.SetResumeSubs(false)
-	})
-	opts.SetConnectionLostHandler(func(c mqtt.Client, e error) {
-		setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
+		opts.SetResumeSubs(true)
 	})
 	})
 	mqclient = mqtt.NewClient(opts)
 	mqclient = mqtt.NewClient(opts)
 	tperiod := time.Now().Add(10 * time.Second)
 	tperiod := time.Now().Add(10 * time.Second)

+ 7 - 4
mq/publishers.go

@@ -15,9 +15,9 @@ import (
 )
 )
 
 
 // PublishHostPeerUpdate --- determines and publishes a peer update to one host
 // PublishHostPeerUpdate --- determines and publishes a peer update to one host
-func PublishHostPeerUpdate(host *models.Host) error {
+func PublishHostPeerUpdate(host *models.Host, nodes []models.Node) error {
 
 
-	peerUpdate, err := logic.GetPeerUpdateForHost(host)
+	peerUpdate, err := logic.GetPeerUpdateForHost(host, nodes)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -711,7 +711,10 @@ func sendPeers() {
 	if err != nil && len(hosts) > 0 {
 	if err != nil && len(hosts) > 0 {
 		logger.Log(1, "error retrieving networks for keepalive", err.Error())
 		logger.Log(1, "error retrieving networks for keepalive", err.Error())
 	}
 	}
-
+	nodes, err := logic.GetAllNodes()
+	if err != nil {
+		return
+	}
 	var force bool
 	var force bool
 	peer_force_send++
 	peer_force_send++
 	if peer_force_send == 5 {
 	if peer_force_send == 5 {
@@ -729,7 +732,7 @@ func sendPeers() {
 		for _, host := range hosts {
 		for _, host := range hosts {
 			host := host
 			host := host
 			logger.Log(2, "sending scheduled peer update (5 min)")
 			logger.Log(2, "sending scheduled peer update (5 min)")
-			if err = PublishHostPeerUpdate(&host); err != nil {
+			if err = PublishHostPeerUpdate(&host, nodes); err != nil {
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 			}
 			}
 		}
 		}

+ 22 - 18
release.md

@@ -1,22 +1,26 @@
 
 
-# Netmaker v0.20.2
-
-## whats new
-- 
-    
-## whats fixed
-- enrollment keys for non-admins 
-- client version displayed correctly in UI
-- upd hole punching improvments
-- SSL fallback to letsencrypt
-- permission handling for non-admin users
+# Netmaker v0.20.4
 
 
+## Whats New
+- Moved to new licensing server for self-hosted
+- STUN removed from netmaker server to improve memory performance
+- Added DB caching to drastically reduce read/writes from disk
 
 
+## What's Fixed
+- Major memory leak resolved due to STUN
+- Issues with netclient ports on daemon restart
+- Windows GUI unable to find netclient backend
+- Major scalability fixes - Can now scale to hundreds of hosts with low resources
+- Resolved ACL panic
+- Reverted blocking creation of Ingress with NAT
+     
 ## known issues
 ## known issues
-- Migration causes a listen port of 0 for some upgraded hosts
-- Docker clients can not re-join after deletion
-- Innacurate Ext Client Metrics 
-- Issue with Mac + IPv6 addressing
-- Nodes on same local network may not always connect
-- List populates egress ranges twice
-- If you do NOT set STUN_LIST on server, it could lead to strange behavior on client
+- netclient-gui (windows) will display an erroneous error dialog when joining a network (can be ignored)
+- netclient-gui will continously display error dialog if netmaker server is offline
+- Incorrect metrics against ext clients
+- Host ListenPorts set to 0 after migration from 0.17.1 -> 0.20.4
+- Mac IPv6 addresses/route issues
+- Docker client can not re-join after complete deletion
+- netclient-gui network tab blank after disconnect
+
+

+ 1 - 2
scripts/nm-certs.sh

@@ -15,7 +15,7 @@ if [ -z "$NM_DOMAIN" ] || [ -z "$NM_EMAIL" ]; then
 fi
 fi
 
 
 # TODO make sure this doesnt break, parse `certbot certificates` if yes
 # TODO make sure this doesnt break, parse `certbot certificates` if yes
-CERT_DIR="$SCRIPT_DIR/letsencrypt/live/stun.$NM_DOMAIN"
+CERT_DIR="$SCRIPT_DIR/letsencrypt/live/api.$NM_DOMAIN"
 
 
 echo "Setting up SSL certificates..."
 echo "Setting up SSL certificates..."
 
 
@@ -31,7 +31,6 @@ CERTBOT_PARAMS=$(cat <<EOF
 certonly --standalone \
 certonly --standalone \
 	--non-interactive --agree-tos \
 	--non-interactive --agree-tos \
 	-m $NM_EMAIL \
 	-m $NM_EMAIL \
-	-d stun.$NM_DOMAIN \
 	-d api.$NM_DOMAIN \
 	-d api.$NM_DOMAIN \
 	-d broker.$NM_DOMAIN \
 	-d broker.$NM_DOMAIN \
 	-d dashboard.$NM_DOMAIN \
 	-d dashboard.$NM_DOMAIN \

+ 12 - 6
scripts/nm-quick.sh

@@ -4,7 +4,7 @@ CONFIG_FILE=netmaker.env
 # location of nm-quick.sh (usually `/root`)
 # location of nm-quick.sh (usually `/root`)
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
-NM_QUICK_VERSION="0.1.0"
+NM_QUICK_VERSION="0.1.1"
 LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
 LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
 
 
 if [ $(id -u) -ne 0 ]; then
 if [ $(id -u) -ne 0 ]; then
@@ -17,11 +17,12 @@ unset BUILD_TYPE
 unset BUILD_TAG
 unset BUILD_TAG
 unset IMAGE_TAG
 unset IMAGE_TAG
 unset AUTO_BUILD
 unset AUTO_BUILD
+unset NETMAKER_BASE_DOMAIN
 
 
 # usage - displays usage instructions
 # usage - displays usage instructions
 usage() {
 usage() {
 	echo "nm-quick.sh v$NM_QUICK_VERSION"
 	echo "nm-quick.sh v$NM_QUICK_VERSION"
-	echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]"
+	echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto] [-d domain]"
 	echo "  -e      if specified, will install netmaker EE"
 	echo "  -e      if specified, will install netmaker EE"
 	echo "  -b      type of build; options:"
 	echo "  -b      type of build; options:"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
@@ -29,14 +30,16 @@ usage() {
 	echo "          \"branch\": - will install a specific branch using remote git and dockerhub"
 	echo "          \"branch\": - will install a specific branch using remote git and dockerhub"
 	echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
 	echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
 	echo "  -a      auto-build; skip prompts and use defaults, if none provided"
 	echo "  -a      auto-build; skip prompts and use defaults, if none provided"
+	echo "  -d      domain; if specified, will use this domain instead of auto-generating one"
 	echo "examples:"
 	echo "examples:"
 	echo "          nm-quick.sh -e -b version -t $LATEST"
 	echo "          nm-quick.sh -e -b version -t $LATEST"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"
 	echo "          nm-quick.sh -e -b branch -t develop"
 	echo "          nm-quick.sh -e -b branch -t develop"
+	echo "          nm-quick.sh -e -b version -t $LATEST -a -d example.com"
 	exit 1
 	exit 1
 }
 }
 
 
-while getopts evab:t: flag; do
+while getopts evab:d:t: flag; do
 	case "${flag}" in
 	case "${flag}" in
 	e)
 	e)
 		INSTALL_TYPE="ee"
 		INSTALL_TYPE="ee"
@@ -60,6 +63,9 @@ while getopts evab:t: flag; do
 	t)
 	t)
 		BUILD_TAG=${OPTARG}
 		BUILD_TAG=${OPTARG}
 		;;
 		;;
+	d)
+		NETMAKER_BASE_DOMAIN=${OPTARG}
+		;;
 	esac
 	esac
 done
 done
 
 
@@ -490,8 +496,9 @@ set_install_vars() {
 	if [ "$IP_ADDR" = "" ]; then
 	if [ "$IP_ADDR" = "" ]; then
 		IP_ADDR=$(curl -s ifconfig.me)
 		IP_ADDR=$(curl -s ifconfig.me)
 	fi
 	fi
-
-	NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then
+		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	fi
 	SERVER_HOST=$IP_ADDR
 	SERVER_HOST=$IP_ADDR
 	if test -z "$MASTER_KEY"; then
 	if test -z "$MASTER_KEY"; then
 		MASTER_KEY=$(
 		MASTER_KEY=$(
@@ -536,7 +543,6 @@ set_install_vars() {
 	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
 	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
-	echo "               stun.$NETMAKER_BASE_DOMAIN"
 	echo "               turn.$NETMAKER_BASE_DOMAIN"
 	echo "               turn.$NETMAKER_BASE_DOMAIN"
 	echo "            turnapi.$NETMAKER_BASE_DOMAIN"
 	echo "            turnapi.$NETMAKER_BASE_DOMAIN"
 
 

+ 1 - 2
scripts/nm-upgrade-0-17-1-to-0-19-0.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.20.2"
+LATEST="v0.20.4"
 INSTALL_PATH="/root"
 INSTALL_PATH="/root"
 
 
 trap restore_old_netmaker_instructions
 trap restore_old_netmaker_instructions
@@ -259,7 +259,6 @@ collect_server_settings() {
     esac
     esac
   done
   done
 
 
-  STUN_DOMAIN="stun.$SERVER_NAME"
   TURN_DOMAIN="turn.$SERVER_NAME"
   TURN_DOMAIN="turn.$SERVER_NAME"
   TURNAPI_DOMAIN="turnapi.$SERVER_NAME"
   TURNAPI_DOMAIN="turnapi.$SERVER_NAME"
   echo "-----------------------------------------------------"
   echo "-----------------------------------------------------"

+ 48 - 83
servercfg/serverconf.go

@@ -10,6 +10,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/config"
 	"github.com/gravitl/netmaker/config"
+
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
 
 
@@ -43,7 +44,6 @@ func GetServerConfig() config.ServerConfig {
 	cfg.AllowedOrigin = GetAllowedOrigin()
 	cfg.AllowedOrigin = GetAllowedOrigin()
 	cfg.RestBackend = "off"
 	cfg.RestBackend = "off"
 	cfg.NodeID = GetNodeID()
 	cfg.NodeID = GetNodeID()
-	cfg.StunPort = GetStunPort()
 	cfg.BrokerType = GetBrokerType()
 	cfg.BrokerType = GetBrokerType()
 	cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
 	cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
 	if AutoUpdateEnabled() {
 	if AutoUpdateEnabled() {
@@ -83,7 +83,6 @@ func GetServerConfig() config.ServerConfig {
 	cfg.FrontendURL = GetFrontendURL()
 	cfg.FrontendURL = GetFrontendURL()
 	cfg.Telemetry = Telemetry()
 	cfg.Telemetry = Telemetry()
 	cfg.Server = GetServer()
 	cfg.Server = GetServer()
-	cfg.StunList = GetStunListString()
 	cfg.Verbosity = GetVerbosity()
 	cfg.Verbosity = GetVerbosity()
 	cfg.IsEE = "no"
 	cfg.IsEE = "no"
 	if Is_EE {
 	if Is_EE {
@@ -108,8 +107,6 @@ func GetServerInfo() models.ServerConfig {
 	}
 	}
 	cfg.Version = GetVersion()
 	cfg.Version = GetVersion()
 	cfg.Is_EE = Is_EE
 	cfg.Is_EE = Is_EE
-	cfg.StunPort = GetStunPort()
-	cfg.StunList = GetStunList()
 	cfg.TurnDomain = GetTurnHost()
 	cfg.TurnDomain = GetTurnHost()
 	cfg.TurnPort = GetTurnPort()
 	cfg.TurnPort = GetTurnPort()
 	cfg.UseTurn = IsUsingTurn()
 	cfg.UseTurn = IsUsingTurn()
@@ -221,46 +218,6 @@ func GetAPIPort() string {
 	return apiport
 	return apiport
 }
 }
 
 
-// GetStunList - gets the stun servers
-func GetStunList() []models.StunServer {
-	stunList := []models.StunServer{
-		{
-			Domain: "stun1.netmaker.io",
-			Port:   3478,
-		},
-		{
-			Domain: "stun2.netmaker.io",
-			Port:   3478,
-		},
-	}
-	parsed := false
-	if os.Getenv("STUN_LIST") != "" {
-		stuns, err := parseStunList(os.Getenv("STUN_LIST"))
-		if err == nil {
-			parsed = true
-			stunList = stuns
-		}
-	}
-	if !parsed && config.Config.Server.StunList != "" {
-		stuns, err := parseStunList(config.Config.Server.StunList)
-		if err == nil {
-			stunList = stuns
-		}
-	}
-	return stunList
-}
-
-// GetStunList - gets the stun servers w/o parsing to struct
-func GetStunListString() string {
-	stunList := "stun1.netmaker.io:3478,stun2.netmaker.io:3478"
-	if os.Getenv("STUN_LIST") != "" {
-		stunList = os.Getenv("STUN_LIST")
-	} else if config.Config.Server.StunList != "" {
-		stunList = config.Config.Server.StunList
-	}
-	return stunList
-}
-
 // GetCoreDNSAddr - gets the core dns address
 // GetCoreDNSAddr - gets the core dns address
 func GetCoreDNSAddr() string {
 func GetCoreDNSAddr() string {
 	addr, _ := GetPublicIP()
 	addr, _ := GetPublicIP()
@@ -662,20 +619,6 @@ func GetNetmakerAccountID() string {
 	return netmakerAccountID
 	return netmakerAccountID
 }
 }
 
 
-// GetStunPort - Get the port to run the stun server on
-func GetStunPort() int {
-	port := 3478 //default
-	if os.Getenv("STUN_PORT") != "" {
-		portInt, err := strconv.Atoi(os.Getenv("STUN_PORT"))
-		if err == nil {
-			port = portInt
-		}
-	} else if config.Config.Server.StunPort != 0 {
-		port = config.Config.Server.StunPort
-	}
-	return port
-}
-
 // GetTurnPort - Get the port to run the turn server on
 // GetTurnPort - Get the port to run the turn server on
 func GetTurnPort() int {
 func GetTurnPort() int {
 	port := 3479 //default
 	port := 3479 //default
@@ -714,32 +657,54 @@ func GetTurnPassword() string {
 
 
 }
 }
 
 
-// parseStunList - turn string into slice of StunServers
-func parseStunList(stunString string) ([]models.StunServer, error) {
-	var err error
-	stunServers := []models.StunServer{}
-	stuns := strings.Split(stunString, ",")
-	if len(stuns) == 0 {
-		return stunServers, errors.New("no stun servers provided")
-	}
-	for _, stun := range stuns {
-		stun = strings.Trim(stun, " ")
-		stunInfo := strings.Split(stun, ":")
-		if len(stunInfo) != 2 {
-			continue
-		}
-		port, err := strconv.Atoi(stunInfo[1])
-		if err != nil || port == 0 {
-			continue
-		}
-		stunServers = append(stunServers, models.StunServer{
-			Domain: stunInfo[0],
-			Port:   port,
-		})
+// GetNetworkLimit - fetches free tier limits on users
+func GetUserLimit() int {
+	var userslimit int
+	if os.Getenv("USERS_LIMIT") != "" {
+		userslimit, _ = strconv.Atoi(os.Getenv("USERS_LIMIT"))
+	} else {
+		userslimit = config.Config.Server.UsersLimit
+	}
+	return userslimit
+}
 
 
+// GetNetworkLimit - fetches free tier limits on networks
+func GetNetworkLimit() int {
+	var networkslimit int
+	if os.Getenv("NETWORKS_LIMIT") != "" {
+		networkslimit, _ = strconv.Atoi(os.Getenv("NETWORKS_LIMIT"))
+	} else {
+		networkslimit = config.Config.Server.NetworksLimit
 	}
 	}
-	if len(stunServers) == 0 {
-		err = errors.New("no stun entries parsable")
+	return networkslimit
+}
+
+// GetClientLimit - fetches free tier limits on ext. clients
+func GetClientLimit() int {
+	var clientsLimit int
+	if os.Getenv("CLIENTS_LIMIT") != "" {
+		clientsLimit, _ = strconv.Atoi(os.Getenv("CLIENTS_LIMIT"))
+	} else {
+		clientsLimit = config.Config.Server.ClientsLimit
+	}
+	return clientsLimit
+}
+
+// GetHostLimit - fetches free tier limits on hosts
+func GetHostLimit() int {
+	var hostsLimit int
+	if os.Getenv("HOSTS_LIMIT") != "" {
+		hostsLimit, _ = strconv.Atoi(os.Getenv("HOSTS_LIMIT"))
+	} else {
+		hostsLimit = config.Config.Server.HostsLimit
+	}
+	return hostsLimit
+}
+
+// DeployedByOperator - returns true if the instance is deployed by netmaker operator
+func DeployedByOperator() bool {
+	if os.Getenv("DEPLOYED_BY_OPERATOR") != "" {
+		return os.Getenv("DEPLOYED_BY_OPERATOR") == "true"
 	}
 	}
-	return stunServers, err
+	return config.Config.Server.DeployedByOperator
 }
 }

+ 0 - 154
stun-server/stun-server.go

@@ -1,154 +0,0 @@
-package stunserver
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"strings"
-	"sync"
-
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/servercfg"
-	"github.com/pkg/errors"
-	"gortc.io/stun"
-)
-
-// Server is RFC 5389 basic server implementation.
-//
-// Current implementation is UDP only and not utilizes FINGERPRINT mechanism,
-// nor ALTERNATE-SERVER, nor credentials mechanisms. It does not support
-// backwards compatibility with RFC 3489.
-type Server struct {
-	Addr string
-}
-
-var (
-	software          = stun.NewSoftware("netmaker-stun")
-	errNotSTUNMessage = errors.New("not stun message")
-)
-
-func basicProcess(addr net.Addr, b []byte, req, res *stun.Message) error {
-	if !stun.IsMessage(b) {
-		return errNotSTUNMessage
-	}
-	if _, err := req.Write(b); err != nil {
-		return errors.Wrap(err, "failed to read message")
-	}
-	var (
-		ip   net.IP
-		port int
-	)
-	switch a := addr.(type) {
-	case *net.UDPAddr:
-		ip = a.IP
-		port = a.Port
-	default:
-		panic(fmt.Sprintf("unknown addr: %v", addr))
-	}
-	return res.Build(req,
-		stun.BindingSuccess,
-		software,
-		&stun.XORMappedAddress{
-			IP:   ip,
-			Port: port,
-		},
-		stun.Fingerprint,
-	)
-}
-
-func (s *Server) serveConn(c net.PacketConn, res, req *stun.Message, ctx context.Context) error {
-	if c == nil {
-		return nil
-	}
-	go func(ctx context.Context) {
-		<-ctx.Done()
-		if c != nil {
-			// kill connection on server shutdown
-			c.Close()
-		}
-	}(ctx)
-
-	buf := make([]byte, 1024)
-	n, addr, err := c.ReadFrom(buf) // this be blocky af
-	if err != nil {
-		if !strings.Contains(err.Error(), "use of closed network connection") {
-			logger.Log(1, "STUN read error:", err.Error())
-		}
-		return nil
-	}
-
-	if _, err = req.Write(buf[:n]); err != nil {
-		logger.Log(1, "STUN write error:", err.Error())
-		return err
-	}
-	if err = basicProcess(addr, buf[:n], req, res); err != nil {
-		if err == errNotSTUNMessage {
-			return nil
-		}
-		logger.Log(1, "STUN process error:", err.Error())
-		return nil
-	}
-	_, err = c.WriteTo(res.Raw, addr)
-	if err != nil {
-		logger.Log(1, "STUN response write error", err.Error())
-	}
-	return err
-}
-
-// Serve reads packets from connections and responds to BINDING requests.
-func (s *Server) serve(c net.PacketConn, ctx context.Context) error {
-	var (
-		res = new(stun.Message)
-		req = new(stun.Message)
-	)
-	for {
-		select {
-		case <-ctx.Done():
-			logger.Log(0, "shut down STUN server")
-			return nil
-		default:
-			if err := s.serveConn(c, res, req, ctx); err != nil {
-				logger.Log(1, "serve: %v", err.Error())
-				continue
-			}
-			res.Reset()
-			req.Reset()
-		}
-	}
-}
-
-// listenUDPAndServe listens on laddr and process incoming packets.
-func listenUDPAndServe(ctx context.Context, serverNet, laddr string) error {
-	c, err := net.ListenPacket(serverNet, laddr)
-	if err != nil {
-		return err
-	}
-	s := &Server{
-		Addr: laddr,
-	}
-	return s.serve(c, ctx)
-}
-
-func normalize(address string) string {
-	if len(address) == 0 {
-		address = "0.0.0.0"
-	}
-	if !strings.Contains(address, ":") {
-		address = fmt.Sprintf("%s:%d", address, stun.DefaultPort)
-	}
-	return address
-}
-
-// Start - starts the stun server
-func Start(wg *sync.WaitGroup, ctx context.Context) {
-	defer wg.Done()
-	normalized := normalize(fmt.Sprintf("0.0.0.0:%d", servercfg.GetStunPort()))
-	logger.Log(0, "netmaker-stun listening on", normalized, "via udp")
-	if err := listenUDPAndServe(ctx, "udp", normalized); err != nil {
-		if strings.Contains(err.Error(), "closed network connection") {
-			logger.Log(0, "shutdown STUN server")
-		} else {
-			logger.Log(0, "server: ", err.Error())
-		}
-	}
-}

+ 1 - 1
swagger.yaml

@@ -704,7 +704,7 @@ info:
 
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
     title: Netmaker
-    version: 0.20.2
+    version: 0.20.4
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: