Browse Source

resolve merge conflicts

abhishek9686 6 months ago
parent
commit
225bf37e02

+ 1 - 1
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -2,7 +2,7 @@ name: Bug Report
 description: File a bug report
 description: File a bug report
 title: "[Bug]: "
 title: "[Bug]: "
 labels: ["bug", "triage"]
 labels: ["bug", "triage"]
-assignees: ["ok-john", "0xdcarns", "afeiszli",  "mattkasun"]
+assignees: ["abhishek9686","VishalDalwadi","Aceix","dentadlp"]
 body:
 body:
   - type: markdown
   - type: markdown
     attributes:
     attributes:

+ 2 - 2
.github/workflows/deletedroplets.yml

@@ -12,7 +12,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     steps:
     steps:
       - name: get logs
       - name: get logs
-        uses: dawidd6/action-download-artifact@v7
+        uses: dawidd6/action-download-artifact@v8
         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
@@ -75,7 +75,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     steps:
     steps:
       - name: get logs
       - name: get logs
-        uses: dawidd6/action-download-artifact@v7
+        uses: dawidd6/action-download-artifact@v8
         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

+ 3 - 1
Dockerfile

@@ -6,11 +6,13 @@ 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.21.0
+FROM alpine:3.21.2
 
 
 # add a c lib
 # add a c lib
 # set the working directory
 # set the working directory
 WORKDIR /root/
 WORKDIR /root/
+RUN apk update && apk upgrade
+RUN apk add --no-cache sqlite
 RUN mkdir -p /etc/netclient/config
 RUN mkdir -p /etc/netclient/config
 COPY --from=builder /app/netmaker .
 COPY --from=builder /app/netmaker .
 COPY --from=builder /app/config config
 COPY --from=builder /app/config config

+ 1 - 1
Dockerfile-quick

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

+ 6 - 6
README.md

@@ -49,14 +49,14 @@
 
 
 # Try Netmaker SaaS  
 # Try Netmaker SaaS  
 
 
-If you're looking for a managed service, you can get started with just few clicks, visit [netmaker.io](https://account.netmaker.io) to create your netmaker server.  
+If you're looking for a managed service, you can get started with just a few clicks, visit [netmaker.io](https://account.netmaker.io) to create your netmaker server.  
 
 
 # Self-Hosted Open Source Quick Start  
 # Self-Hosted Open Source Quick Start  
 
 
-These are the instructions for deploying a Netmaker server on your own cloud VM as quickly as possible. For more detailed instructions, visit the [Install Docs](https://docs.netmaker.io/docs/server-installation/quick-install#quick-install-script).  
+These are the instructions for deploying a Netmaker server on your cloud VM as quickly as possible. For more detailed instructions, visit the [Install Docs](https://docs.netmaker.io/docs/server-installation/quick-install#quick-install-script).  
 
 
-1. Get a cloud VM with Ubuntu 22.04 and a public IP.
-2. Open ports 443, 80, 3479, 8089 and 51821-51830/udp on the VM firewall and in cloud security settings.
+1. Get a cloud VM with Ubuntu 24.04 and a static public IP.
+2. Allow inbound traffic on port 443,51821 TCP and UDP to the VM firewall in cloud security settings, and for simplicity, allow outbound on All TCP and All UDP.
 3. (recommended) Prepare DNS - Set a wildcard subdomain in your DNS settings for Netmaker, e.g. *.netmaker.example.com, which points to your VM's public IP.
 3. (recommended) Prepare DNS - Set a wildcard subdomain in your DNS settings for Netmaker, e.g. *.netmaker.example.com, which points to your VM's public IP.
 4. Run the script to setup open source version of Netmaker: 
 4. Run the script to setup open source version of Netmaker: 
 
 
@@ -82,11 +82,11 @@ After installing Netmaker, check out the [Walkthrough](https://itnext.io/getting
 
 
 # Why Netmaker + WireGuard?
 # Why Netmaker + WireGuard?
 
 
-- Netmaker automates virtual networks between data centers, clouds, and edge devices, so you don't have to.
+- Netmaker automates virtual networks between data centres, clouds, and edge devices, so you don't have to.
 
 
 - Kernel WireGuard offers maximum speed, performance, and security. 
 - Kernel WireGuard offers maximum speed, performance, and security. 
 
 
-- Netmaker is built to scale from the small business to the enterprise. 
+- Netmaker is built to scale from small businesses to enterprises. 
 
 
 - Netmaker with WireGuard can be highly customized for peer-to-peer, site-to-site, Kubernetes, and more.
 - Netmaker with WireGuard can be highly customized for peer-to-peer, site-to-site, Kubernetes, and more.
 
 

+ 2 - 5
compose/docker-compose.yml

@@ -52,11 +52,8 @@ services:
       - caddy_data:/data
       - caddy_data:/data
       - caddy_conf:/config
       - caddy_conf:/config
     ports:
     ports:
-      - "$SERVER_HOST:80:80/tcp"
-      - "$SERVER_HOST:443:443/tcp"
-    #uncomment to enable IPv6 communication
-     # - "$SERVER_HOST6:80:80/tcp"
-     # - "$SERVER_HOST6:443:443/tcp"
+      - "80:80/tcp"
+      - "443:443/tcp"
 
 
   coredns:
   coredns:
     #network_mode: host
     #network_mode: host

+ 8 - 7
config/config.go

@@ -37,7 +37,7 @@ type ServerConfig struct {
 	APIConnString              string        `yaml:"apiconn"`
 	APIConnString              string        `yaml:"apiconn"`
 	APIHost                    string        `yaml:"apihost"`
 	APIHost                    string        `yaml:"apihost"`
 	APIPort                    string        `yaml:"apiport"`
 	APIPort                    string        `yaml:"apiport"`
-	Broker                     string        `yam:"broker"`
+	Broker                     string        `yaml:"broker"`
 	ServerBrokerEndpoint       string        `yaml:"serverbrokerendpoint"`
 	ServerBrokerEndpoint       string        `yaml:"serverbrokerendpoint"`
 	BrokerType                 string        `yaml:"brokertype"`
 	BrokerType                 string        `yaml:"brokertype"`
 	EmqxRestEndpoint           string        `yaml:"emqxrestendpoint"`
 	EmqxRestEndpoint           string        `yaml:"emqxrestendpoint"`
@@ -92,14 +92,15 @@ type ServerConfig struct {
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
-	EndpointDetection          bool          `json:"endpoint_detection"`
+	EndpointDetection          bool          `yaml:"endpoint_detection"`
 	AllowedEmailDomains        string        `yaml:"allowed_email_domains"`
 	AllowedEmailDomains        string        `yaml:"allowed_email_domains"`
-	EmailSenderAddr            string        `json:"email_sender_addr"`
-	EmailSenderUser            string        `json:"email_sender_user"`
-	EmailSenderPassword        string        `json:"email_sender_password"`
-	SmtpHost                   string        `json:"smtp_host"`
-	SmtpPort                   int           `json:"smtp_port"`
+	EmailSenderAddr            string        `yaml:"email_sender_addr"`
+	EmailSenderUser            string        `yaml:"email_sender_user"`
+	EmailSenderPassword        string        `yaml:"email_sender_password"`
+	SmtpHost                   string        `yaml:"smtp_host"`
+	SmtpPort                   int           `yaml:"smtp_port"`
 	MetricInterval             string        `yaml:"metric_interval"`
 	MetricInterval             string        `yaml:"metric_interval"`
+	MetricsPort                int           `yaml:"metrics_port"`
 	ManageDNS                  bool          `yaml:"manage_dns"`
 	ManageDNS                  bool          `yaml:"manage_dns"`
 	Stun                       bool          `yaml:"stun"`
 	Stun                       bool          `yaml:"stun"`
 	StunServers                string        `yaml:"stun_servers"`
 	StunServers                string        `yaml:"stun_servers"`

+ 1 - 0
controllers/controller.go

@@ -24,6 +24,7 @@ var HttpMiddlewares = []mux.MiddlewareFunc{
 // HttpHandlers - handler functions for REST interactions
 // HttpHandlers - handler functions for REST interactions
 var HttpHandlers = []interface{}{
 var HttpHandlers = []interface{}{
 	nodeHandlers,
 	nodeHandlers,
+	gwHandlers,
 	userHandlers,
 	userHandlers,
 	networkHandlers,
 	networkHandlers,
 	dnsHandlers,
 	dnsHandlers,

+ 207 - 0
controllers/gateway.go

@@ -0,0 +1,207 @@
+package controller
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/google/uuid"
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
+)
+
+func gwHandlers(r *mux.Router) {
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/gateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/gateway", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete)
+	// old relay handlers
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createGateway))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete)
+}
+
+// @Summary     Create a gateway
+// @Router      /api/nodes/{network}/{nodeid}/gateway [post]
+// @Tags        Nodes
+// @Security    oauth2
+// @Success     200 {object} models.ApiNode
+// @Failure     500 {object} models.ErrorResponse
+func createGateway(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	nodeid := params["nodeid"]
+	netid := params["network"]
+	node, err := logic.ValidateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var req models.CreateGwReq
+	err = json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	node, err = logic.CreateIngressGateway(netid, nodeid, req.IngressRequest)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to create gateway on node [%s] on network [%s]: %v",
+				nodeid, netid, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	req.RelayRequest.NetID = netid
+	req.RelayRequest.NodeID = nodeid
+	_, relayNode, err := logic.CreateRelay(req.RelayRequest)
+	if err != nil {
+		logger.Log(
+			0,
+			r.Header.Get("user"),
+			fmt.Sprintf(
+				"failed to create relay on node [%s] on network [%s]: %v",
+				req.RelayRequest.NodeID,
+				req.RelayRequest.NetID,
+				err,
+			),
+		)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	for _, relayedNodeID := range relayNode.RelayedNodes {
+		relayedNode, err := logic.GetNodeByID(relayedNodeID)
+		if err == nil {
+			if relayedNode.FailedOverBy != uuid.Nil {
+				go logic.ResetFailedOverPeer(&relayedNode)
+			}
+
+		}
+	}
+	logger.Log(
+		1,
+		r.Header.Get("user"),
+		"created gw node",
+		req.RelayRequest.NodeID,
+		"on network",
+		req.RelayRequest.NetID,
+	)
+	apiNode := relayNode.ConvertToAPINode()
+
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNode)
+	go func() {
+		if err := mq.NodeUpdate(&node); err != nil {
+			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+		}
+		mq.PublishPeerUpdate(false)
+	}()
+
+}
+
+// @Summary     Delete a gateway
+// @Router      /api/nodes/{network}/{nodeid}/gateway [delete]
+// @Tags        Nodes
+// @Security    oauth2
+// @Success     200 {object} models.ApiNode
+// @Failure     500 {object} models.ErrorResponse
+func deleteGateway(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	nodeid := params["nodeid"]
+	netid := params["network"]
+	node, err := logic.ValidateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	node, removedClients, err := logic.DeleteIngressGateway(nodeid)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to delete ingress gateway on node [%s] on network [%s]: %v",
+				nodeid, netid, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	updateNodes, node, err := logic.DeleteRelay(netid, nodeid)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	node, err = logic.GetNodeByID(node.ID.String())
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to get node", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	node.IsGw = false
+	logic.UpsertNode(&node)
+	logger.Log(1, r.Header.Get("user"), "deleted gw", nodeid, "on network", netid)
+
+	go func() {
+		host, err := logic.GetHost(node.HostID.String())
+		if err == nil {
+			allNodes, err := logic.GetAllNodes()
+			if err != nil {
+				return
+			}
+
+			for _, relayedNode := range updateNodes {
+				err = mq.NodeUpdate(&relayedNode)
+				if err != nil {
+					logger.Log(
+						1,
+						"relayed node update ",
+						relayedNode.ID.String(),
+						"on network",
+						relayedNode.Network,
+						": ",
+						err.Error(),
+					)
+
+				}
+				h, err := logic.GetHost(relayedNode.HostID.String())
+				if err == nil {
+					if h.OS == models.OS_Types.IoT {
+						nodes, err := logic.GetAllNodes()
+						if err != nil {
+							return
+						}
+						node.IsRelay = true // for iot update to recognise that it has to delete relay peer
+						if err = mq.PublishSingleHostPeerUpdate(h, nodes, &node, nil, false, nil); err != nil {
+							logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error())
+						}
+					}
+				}
+			}
+			if len(removedClients) > 0 {
+				if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:], false, nil); err != nil {
+					slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err)
+				}
+			}
+			mq.PublishPeerUpdate(false)
+			if err := mq.NodeUpdate(&node); err != nil {
+				slog.Error(
+					"error publishing node update to node",
+					"node",
+					node.ID,
+					"error",
+					err,
+				)
+			}
+			if servercfg.IsDNSMode() {
+				logic.SetDNS()
+			}
+
+		}
+
+	}()
+
+	apiNode := node.ConvertToAPINode()
+	logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid)
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNode)
+}

+ 136 - 12
controllers/hosts.go

@@ -5,7 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
-	"os"
+	"time"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
@@ -24,6 +24,10 @@ func hostHandlers(r *mux.Router) {
 		Methods(http.MethodGet)
 		Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).
 		Methods(http.MethodPut)
 		Methods(http.MethodPut)
+	r.HandleFunc("/api/hosts/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHosts))).
+		Methods(http.MethodPost)
+	r.HandleFunc("/api/hosts/upgrade", logic.SecurityCheck(true, http.HandlerFunc(upgradeHosts))).
+		Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).
 	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).
 		Methods(http.MethodPut)
 		Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).
 	r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).
@@ -45,16 +49,64 @@ func hostHandlers(r *mux.Router) {
 		Methods(http.MethodPost)
 		Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))).
 	r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))).
 		Methods(http.MethodPut)
 		Methods(http.MethodPut)
+	r.HandleFunc("/api/v1/host/{hostid}/peer_info", Authorize(true, false, "host", http.HandlerFunc(getHostPeerInfo))).
+		Methods(http.MethodGet)
 	r.HandleFunc("/api/emqx/hosts", logic.SecurityCheck(true, http.HandlerFunc(delEmqxHosts))).
 	r.HandleFunc("/api/emqx/hosts", logic.SecurityCheck(true, http.HandlerFunc(delEmqxHosts))).
 		Methods(http.MethodDelete)
 		Methods(http.MethodDelete)
 	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 }
 }
 
 
+// @Summary     Requests all the hosts to upgrade their version
+// @Router      /api/hosts/upgrade [post]
+// @Tags        Hosts
+// @Security    oauth
+// @Param       force query bool false "Force upgrade"
+// @Success     200 {string} string "upgrade all hosts request received"
+func upgradeHosts(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	action := models.Upgrade
+
+	if r.URL.Query().Get("force") == "true" {
+		action = models.ForceUpgrade
+	}
+
+	user := r.Header.Get("user")
+
+	go func() {
+		slog.Info("requesting all hosts to upgrade", "user", user)
+
+		hosts, err := logic.GetAllHosts()
+		if err != nil {
+			slog.Error("failed to retrieve all hosts", "user", user, "error", err)
+			return
+		}
+
+		for _, host := range hosts {
+			go func(host models.Host) {
+				hostUpdate := models.HostUpdate{
+					Action: action,
+					Host:   host,
+				}
+				if err = mq.HostUpdate(&hostUpdate); err != nil {
+					slog.Error("failed to request host to upgrade", "user", user, "host", host.ID.String(), "error", err)
+				} else {
+					slog.Info("host upgrade requested", "user", user, "host", host.ID.String())
+				}
+			}(host)
+		}
+	}()
+
+	slog.Info("upgrade all hosts request received", "user", user)
+	logic.ReturnSuccessResponse(w, r, "upgrade all hosts request received")
+}
+
 // @Summary     Upgrade a host
 // @Summary     Upgrade a host
 // @Router      /api/hosts/{hostid}/upgrade [put]
 // @Router      /api/hosts/{hostid}/upgrade [put]
 // @Tags        Hosts
 // @Tags        Hosts
 // @Security    oauth
 // @Security    oauth
 // @Param       hostid path string true "Host ID"
 // @Param       hostid path string true "Host ID"
+// @Param       force query bool false "Force upgrade"
 // @Success     200 {string} string "passed message to upgrade host"
 // @Success     200 {string} string "passed message to upgrade host"
 // @Failure     500 {object} models.ErrorResponse
 // @Failure     500 {object} models.ErrorResponse
 // upgrade host is a handler to send upgrade message to a host
 // upgrade host is a handler to send upgrade message to a host
@@ -65,7 +117,14 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "notfound"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "notfound"))
 		return
 		return
 	}
 	}
-	if err := mq.HostUpdate(&models.HostUpdate{Action: models.Upgrade, Host: *host}); err != nil {
+
+	action := models.Upgrade
+
+	if r.URL.Query().Get("force") == "true" {
+		action = models.ForceUpgrade
+	}
+
+	if err := mq.HostUpdate(&models.HostUpdate{Action: action, Host: *host}); err != nil {
 		slog.Error("failed to upgrade host", "error", err)
 		slog.Error("failed to upgrade host", "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
@@ -176,17 +235,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
 			slog.Error("failed to get node:", "id", node.ID, "error", err)
 			slog.Error("failed to get node:", "id", node.ID, "error", err)
 			continue
 			continue
 		}
 		}
-		if node.FailedOverBy != uuid.Nil {
+		if node.FailedOverBy != uuid.Nil && r.URL.Query().Get("reset_failovered") == "true" {
 			logic.ResetFailedOverPeer(&node)
 			logic.ResetFailedOverPeer(&node)
 			sendPeerUpdate = true
 			sendPeerUpdate = true
 		}
 		}
 	}
 	}
 	if sendPeerUpdate {
 	if sendPeerUpdate {
-		reset := true
-		if os.Getenv("RESET_PEER_UPDATE") != "" {
-			reset = os.Getenv("RESET_PEER_UPDATE") == "true"
-		}
-		if err := mq.PublishPeerUpdate(reset); err != nil {
+		if err := mq.PublishPeerUpdate(false); err != nil {
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 		}
 		}
 	}
 	}
@@ -320,7 +375,7 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID)
+	slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID, "action", hostUpdate.Action)
 	switch hostUpdate.Action {
 	switch hostUpdate.Action {
 	case models.CheckIn:
 	case models.CheckIn:
 		sendPeerUpdate = mq.HandleHostCheckin(&hostUpdate.Host, currentHost)
 		sendPeerUpdate = mq.HandleHostCheckin(&hostUpdate.Host, currentHost)
@@ -537,7 +592,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 					w,
 					w,
 					r,
 					r,
 					logic.FormatError(
 					logic.FormatError(
-						fmt.Errorf("failed to force delete daemon node: "+err.Error()),
+						fmt.Errorf("failed to force delete daemon node: %s", err.Error()),
 						"internal",
 						"internal",
 					),
 					),
 				)
 				)
@@ -577,7 +632,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 					w,
 					w,
 					r,
 					r,
 					logic.FormatError(
 					logic.FormatError(
-						fmt.Errorf("failed to force delete daemon node: "+err.Error()),
+						fmt.Errorf("failed to force delete daemon node: %s", err.Error()),
 						"internal",
 						"internal",
 					),
 					),
 				)
 				)
@@ -860,6 +915,45 @@ func updateKeys(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 }
 }
 
 
+// @Summary     Requests all the hosts to pull
+// @Router      /api/hosts/sync [post]
+// @Tags        Hosts
+// @Security    oauth
+// @Success     200 {string} string "sync all hosts request received"
+func syncHosts(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	user := r.Header.Get("user")
+
+	go func() {
+		slog.Info("requesting all hosts to sync", "user", user)
+
+		hosts, err := logic.GetAllHosts()
+		if err != nil {
+			slog.Error("failed to retrieve all hosts", "user", user, "error", err)
+			return
+		}
+
+		for _, host := range hosts {
+			go func(host models.Host) {
+				hostUpdate := models.HostUpdate{
+					Action: models.RequestPull,
+					Host:   host,
+				}
+				if err = mq.HostUpdate(&hostUpdate); err != nil {
+					slog.Error("failed to request host to sync", "user", user, "host", host.ID.String(), "error", err)
+				} else {
+					slog.Info("host sync requested", "user", user, "host", host.ID.String())
+				}
+			}(host)
+			time.Sleep(time.Millisecond * 100)
+		}
+	}()
+
+	slog.Info("sync all hosts request received", "user", user)
+	logic.ReturnSuccessResponse(w, r, "sync all hosts request received")
+}
+
 // @Summary     Requests a host to pull
 // @Summary     Requests a host to pull
 // @Router      /api/hosts/{hostid}/sync [post]
 // @Router      /api/hosts/{hostid}/sync [post]
 // @Tags        Hosts
 // @Tags        Hosts
@@ -892,7 +986,7 @@ func syncHost(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}()
 	}()
 
 
-	slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID)
+	slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID.String())
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 }
 }
 
 
@@ -927,3 +1021,33 @@ func delEmqxHosts(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	logic.ReturnSuccessResponse(w, r, "deleted hosts data on emqx")
 	logic.ReturnSuccessResponse(w, r, "deleted hosts data on emqx")
 }
 }
+
+// @Summary     Fetches host peerinfo
+// @Router      /api/host/{hostid}/peer_info [get]
+// @Tags        Hosts
+// @Security    oauth
+// @Param       hostid path string true "Host ID"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     500 {object} models.ErrorResponse
+func getHostPeerInfo(w http.ResponseWriter, r *http.Request) {
+	hostId := mux.Vars(r)["hostid"]
+	var errorResponse = models.ErrorResponse{}
+
+	host, err := logic.GetHost(hostId)
+	if err != nil {
+		slog.Error("failed to retrieve host", "error", err)
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logic.ReturnErrorResponse(w, r, errorResponse)
+		return
+	}
+	peerInfo, err := logic.GetHostPeerInfo(host)
+	if err != nil {
+		slog.Error("failed to retrieve host peerinfo", "error", err)
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logic.ReturnErrorResponse(w, r, errorResponse)
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, peerInfo, "fetched host peer info")
+}

+ 27 - 3
controllers/network.go

@@ -434,6 +434,7 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 // @Tags        Networks
 // @Tags        Networks
 // @Security    oauth
 // @Security    oauth
 // @Param       networkname path string true "Network name"
 // @Param       networkname path string true "Network name"
+// @Param       force query bool false "Force Delete"
 // @Produce     json
 // @Produce     json
 // @Success     200 {object} models.SuccessResponse
 // @Success     200 {object} models.SuccessResponse
 // @Failure     400 {object} models.ErrorResponse
 // @Failure     400 {object} models.ErrorResponse
@@ -441,10 +442,18 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
-
+	force := r.URL.Query().Get("force") == "true"
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	network := params["networkname"]
 	network := params["networkname"]
-	err := logic.DeleteNetwork(network)
+	doneCh := make(chan struct{}, 1)
+	networkNodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to get network nodes [%s]: %v", network, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	err = logic.DeleteNetwork(network, force, doneCh)
 	if err != nil {
 	if err != nil {
 		errtype := "badrequest"
 		errtype := "badrequest"
 		if strings.Contains(err.Error(), "Node check failed") {
 		if strings.Contains(err.Error(), "Node check failed") {
@@ -459,7 +468,22 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 	go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
 	go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
 	//delete network from allocated ip map
 	//delete network from allocated ip map
 	go logic.RemoveNetworkFromAllocatedIpMap(network)
 	go logic.RemoveNetworkFromAllocatedIpMap(network)
-
+	go func() {
+		<-doneCh
+		mq.PublishPeerUpdate(true)
+		// send node update to clean up locally
+		for _, node := range networkNodes {
+			node := node
+			node.PendingDelete = true
+			node.Action = models.NODE_DELETE
+			if err := mq.NodeUpdate(&node); err != nil {
+				slog.Error("error publishing node update to node", "node", node.ID, "error", err)
+			}
+		}
+		if servercfg.IsDNSMode() {
+			logic.SetDNS()
+		}
+	}()
 	logger.Log(1, r.Header.Get("user"), "deleted network", network)
 	logger.Log(1, r.Header.Get("user"), "deleted network", network)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode("success")
 	json.NewEncoder(w).Encode("success")

+ 19 - 2
controllers/network_test.go

@@ -75,11 +75,19 @@ func TestDeleteNetwork(t *testing.T) {
 	t.Run("NetworkwithNodes", func(t *testing.T) {
 	t.Run("NetworkwithNodes", func(t *testing.T) {
 	})
 	})
 	t.Run("DeleteExistingNetwork", func(t *testing.T) {
 	t.Run("DeleteExistingNetwork", func(t *testing.T) {
-		err := logic.DeleteNetwork("skynet")
+		doneCh := make(chan struct{}, 1)
+		err := logic.DeleteNetwork("skynet", false, doneCh)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 	t.Run("NonExistentNetwork", func(t *testing.T) {
 	t.Run("NonExistentNetwork", func(t *testing.T) {
-		err := logic.DeleteNetwork("skynet")
+		doneCh := make(chan struct{}, 1)
+		err := logic.DeleteNetwork("skynet", false, doneCh)
+		assert.Nil(t, err)
+	})
+	createNetv1("test")
+	t.Run("ForceDeleteNetwork", func(t *testing.T) {
+		doneCh := make(chan struct{}, 1)
+		err := logic.DeleteNetwork("test", true, doneCh)
 		assert.Nil(t, err)
 		assert.Nil(t, err)
 	})
 	})
 }
 }
@@ -214,6 +222,15 @@ func createNet() {
 		logic.CreateNetwork(network)
 		logic.CreateNetwork(network)
 	}
 	}
 }
 }
+func createNetv1(netId string) {
+	var network models.Network
+	network.NetID = netId
+	network.AddressRange = "100.0.0.1/24"
+	_, err := logic.GetNetwork(netId)
+	if err != nil {
+		logic.CreateNetwork(network)
+	}
+}
 
 
 func createNetDualStack() {
 func createNetDualStack() {
 	var network models.Network
 	var network models.Network

+ 2 - 109
controllers/node.go

@@ -28,8 +28,8 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 }
@@ -548,113 +548,6 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	}()
 	}()
 }
 }
 
 
-// == INGRESS ==
-
-// @Summary     Create an remote access gateway
-// @Router      /api/nodes/{network}/{nodeid}/createingress [post]
-// @Tags        Nodes
-// @Security    oauth2
-// @Success     200 {object} models.ApiNode
-// @Failure     500 {object} models.ErrorResponse
-func createIngressGateway(w http.ResponseWriter, r *http.Request) {
-	var params = mux.Vars(r)
-	w.Header().Set("Content-Type", "application/json")
-	nodeid := params["nodeid"]
-	netid := params["network"]
-	node, err := logic.ValidateParams(nodeid, netid)
-	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	var request models.IngressRequest
-	json.NewDecoder(r.Body).Decode(&request)
-	node, err = logic.CreateIngressGateway(netid, nodeid, request)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to create ingress gateway on node [%s] on network [%s]: %v",
-				nodeid, netid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
-	apiNode := node.ConvertToAPINode()
-	logger.Log(
-		1,
-		r.Header.Get("user"),
-		"created ingress gateway on node",
-		nodeid,
-		"on network",
-		netid,
-	)
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-	go func() {
-		if err := mq.NodeUpdate(&node); err != nil {
-			slog.Error("error publishing node update to node", "node", node.ID, "error", err)
-		}
-		mq.PublishPeerUpdate(false)
-	}()
-}
-
-// @Summary     Delete an remote access gateway
-// @Router      /api/nodes/{network}/{nodeid}/deleteingress [delete]
-// @Tags        Nodes
-// @Security    oauth2
-// @Success     200 {object} models.ApiNode
-// @Failure     500 {object} models.ErrorResponse
-func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/json")
-	var params = mux.Vars(r)
-	nodeid := params["nodeid"]
-	netid := params["network"]
-	node, err := logic.ValidateParams(nodeid, netid)
-	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	node, removedClients, err := logic.DeleteIngressGateway(nodeid)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to delete ingress gateway on node [%s] on network [%s]: %v",
-				nodeid, netid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
-	apiNode := node.ConvertToAPINode()
-	logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid)
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-
-	if len(removedClients) > 0 {
-		host, err := logic.GetHost(node.HostID.String())
-		if err == nil {
-			allNodes, err := logic.GetAllNodes()
-			if err != nil {
-				return
-			}
-			go func() {
-				if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:], false, nil); err != nil {
-					slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err)
-				}
-				mq.PublishPeerUpdate(false)
-				if err := mq.NodeUpdate(&node); err != nil {
-					slog.Error(
-						"error publishing node update to node",
-						"node",
-						node.ID,
-						"error",
-						err,
-					)
-				}
-				if servercfg.IsDNSMode() {
-					logic.SetDNS()
-				}
-			}()
-		}
-	}
-}
-
 // @Summary     Update an individual node
 // @Summary     Update an individual node
 // @Router      /api/nodes/{network}/{nodeid} [put]
 // @Router      /api/nodes/{network}/{nodeid} [put]
 // @Tags        Nodes
 // @Tags        Nodes

+ 3 - 0
database/database.go

@@ -71,6 +71,8 @@ const (
 	USER_INVITES_TABLE_NAME = "user_invites"
 	USER_INVITES_TABLE_NAME = "user_invites"
 	// TAG_TABLE_NAME - table for tags
 	// TAG_TABLE_NAME - table for tags
 	TAG_TABLE_NAME = "tags"
 	TAG_TABLE_NAME = "tags"
+	// PEER_ACK_TABLE - table for failover peer ack
+	PEER_ACK_TABLE = "peer_ack"
 	// == ERROR CONSTS ==
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
 	// NO_RECORD - no singular result found
 	NO_RECORD = "no result found"
 	NO_RECORD = "no result found"
@@ -158,6 +160,7 @@ func createTables() {
 	CreateTable(USER_INVITES_TABLE_NAME)
 	CreateTable(USER_INVITES_TABLE_NAME)
 	CreateTable(TAG_TABLE_NAME)
 	CreateTable(TAG_TABLE_NAME)
 	CreateTable(ACLS_TABLE_NAME)
 	CreateTable(ACLS_TABLE_NAME)
+	CreateTable(PEER_ACK_TABLE)
 }
 }
 
 
 func CreateTable(tableName string) error {
 func CreateTable(tableName string) error {

+ 5 - 5
go.mod

@@ -5,7 +5,7 @@ go 1.23
 require (
 require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.23.0
+	github.com/go-playground/validator/v10 v10.24.0
 	github.com/golang-jwt/jwt/v4 v4.5.1
 	github.com/golang-jwt/jwt/v4 v4.5.1
 	github.com/google/uuid v1.6.0
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/handlers v1.5.2
@@ -18,10 +18,10 @@ require (
 	github.com/stretchr/testify v1.10.0
 	github.com/stretchr/testify v1.10.0
 	github.com/txn2/txeh v1.5.5
 	github.com/txn2/txeh v1.5.5
 	go.uber.org/automaxprocs v1.6.0
 	go.uber.org/automaxprocs v1.6.0
-	golang.org/x/crypto v0.30.0
-	golang.org/x/net v0.27.0 // indirect
+	golang.org/x/crypto v0.32.0
+	golang.org/x/net v0.34.0 // indirect
 	golang.org/x/oauth2 v0.24.0
 	golang.org/x/oauth2 v0.24.0
-	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	gopkg.in/yaml.v3 v3.0.1
 	gopkg.in/yaml.v3 v3.0.1
@@ -50,7 +50,7 @@ require (
 
 
 require (
 require (
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/kr/text v0.2.0 // indirect

+ 10 - 10
go.sum

@@ -17,8 +17,8 @@ github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQ
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
 github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
 github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -27,8 +27,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
-github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
+github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
 github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
 github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
 github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
@@ -101,8 +101,8 @@ go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwE
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
-golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
-golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -112,8 +112,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
-golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
 golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
 golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
 golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -129,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

+ 2 - 0
logic/acls.go

@@ -164,6 +164,7 @@ func storeAclInCache(a models.Acl) {
 	aclCacheMutex.Lock()
 	aclCacheMutex.Lock()
 	defer aclCacheMutex.Unlock()
 	defer aclCacheMutex.Unlock()
 	aclCacheMap[a.ID] = a
 	aclCacheMap[a.ID] = a
+
 }
 }
 
 
 func removeAclFromCache(a models.Acl) {
 func removeAclFromCache(a models.Acl) {
@@ -599,6 +600,7 @@ func IsPeerAllowed(node, peer models.Node, checkDefaultPolicy bool) bool {
 				return true
 				return true
 			}
 			}
 		}
 		}
+
 	}
 	}
 	// list device policies
 	// list device policies
 	policies := listDevicePolicies(models.NetworkID(peer.Network))
 	policies := listDevicePolicies(models.NetworkID(peer.Network))

+ 0 - 2
logic/extpeers.go

@@ -471,9 +471,7 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 	nodes, _ := GetNetworkNodes(node.Network)
 	nodes, _ := GetNetworkNodes(node.Network)
 	nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...)
 	nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...)
-	//fmt.Printf("=====> NODES: %+v \n\n", nodes)
 	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network))
 	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network))
-	//fmt.Printf("=====> USER NODES %+v \n\n", userNodes)
 	for _, userNodeI := range userNodes {
 	for _, userNodeI := range userNodes {
 		for _, peer := range nodes {
 		for _, peer := range nodes {
 			if peer.IsUserNode {
 			if peer.IsUserNode {

+ 6 - 2
logic/gateway.go

@@ -141,14 +141,14 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	if node.IsRelayed {
 	if node.IsRelayed {
-		return models.Node{}, errors.New("ingress cannot be created on a relayed node")
+		return models.Node{}, errors.New("gateway cannot be created on a relayed node")
 	}
 	}
 	host, err := GetHost(node.HostID.String())
 	host, err := GetHost(node.HostID.String())
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	if host.OS != "linux" {
 	if host.OS != "linux" {
-		return models.Node{}, errors.New("ingress can only be created on linux based node")
+		return models.Node{}, errors.New("gateway can only be created on linux based node")
 	}
 	}
 
 
 	network, err := GetParentNetwork(netid)
 	network, err := GetParentNetwork(netid)
@@ -156,12 +156,16 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	node.IsIngressGateway = true
 	node.IsIngressGateway = true
+	node.IsGw = true
 	if !servercfg.IsPro {
 	if !servercfg.IsPro {
 		node.IsInternetGateway = ingress.IsInternetGateway
 		node.IsInternetGateway = ingress.IsInternetGateway
 	}
 	}
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange6 = network.AddressRange6
 	node.IngressGatewayRange6 = network.AddressRange6
 	node.IngressDNS = ingress.ExtclientDNS
 	node.IngressDNS = ingress.ExtclientDNS
+	if node.IsInternetGateway && node.IngressDNS == "" {
+		node.IngressDNS = "1.1.1.1"
+	}
 	node.IngressPersistentKeepalive = 20
 	node.IngressPersistentKeepalive = 20
 	if ingress.PersistentKeepalive != 0 {
 	if ingress.PersistentKeepalive != 0 {
 		node.IngressPersistentKeepalive = ingress.PersistentKeepalive
 		node.IngressPersistentKeepalive = ingress.PersistentKeepalive

+ 46 - 18
logic/networks.go

@@ -118,7 +118,7 @@ func AddNetworkToAllocatedIpMap(networkName string) {
 		return
 		return
 	}
 	}
 	networkCacheMutex.Lock()
 	networkCacheMutex.Lock()
-	allocatedIpMap[networkName] = map[string]net.IP{}
+	allocatedIpMap[networkName] = make(map[string]net.IP)
 	networkCacheMutex.Unlock()
 	networkCacheMutex.Unlock()
 }
 }
 
 
@@ -190,23 +190,8 @@ func GetNetworks() ([]models.Network, error) {
 }
 }
 
 
 // DeleteNetwork - deletes a network
 // DeleteNetwork - deletes a network
-func DeleteNetwork(network string) error {
-	// remove ACL for network
-	err := nodeacls.DeleteACLContainer(nodeacls.NetworkID(network))
-	if err != nil {
-		logger.Log(1, "failed to remove the node acls during network delete for network,", network)
-	}
-	// Delete default network enrollment key
-	keys, _ := GetAllEnrollmentKeys()
-	for _, key := range keys {
-		if key.Tags[0] == network {
-			if key.Default {
-				DeleteEnrollmentKey(key.Value, true)
-				break
-			}
+func DeleteNetwork(network string, force bool, done chan struct{}) error {
 
 
-		}
-	}
 	nodeCount, err := GetNetworkNonServerNodeCount(network)
 	nodeCount, err := GetNetworkNonServerNodeCount(network)
 	if nodeCount == 0 || database.IsEmptyRecord(err) {
 	if nodeCount == 0 || database.IsEmptyRecord(err) {
 		// delete server nodes first then db records
 		// delete server nodes first then db records
@@ -219,7 +204,50 @@ func DeleteNetwork(network string) error {
 		}
 		}
 		return nil
 		return nil
 	}
 	}
-	return errors.New("node check failed. All nodes must be deleted before deleting network")
+
+	// Remove All Nodes
+	go func() {
+		nodes, err := GetNetworkNodes(network)
+		if err == nil {
+			for _, node := range nodes {
+				node := node
+				host, err := GetHost(node.HostID.String())
+				if err != nil {
+					continue
+				}
+				DissasociateNodeFromHost(&node, host)
+			}
+		}
+		// remove ACL for network
+		err = nodeacls.DeleteACLContainer(nodeacls.NetworkID(network))
+		if err != nil {
+			logger.Log(1, "failed to remove the node acls during network delete for network,", network)
+		}
+		// delete server nodes first then db records
+		err = database.DeleteRecord(database.NETWORKS_TABLE_NAME, network)
+		if err != nil {
+			return
+		}
+		if servercfg.CacheEnabled() {
+			deleteNetworkFromCache(network)
+		}
+		done <- struct{}{}
+		close(done)
+	}()
+
+	// Delete default network enrollment key
+	keys, _ := GetAllEnrollmentKeys()
+	for _, key := range keys {
+		if key.Tags[0] == network {
+			if key.Default {
+				DeleteEnrollmentKey(key.Value, true)
+				break
+			}
+
+		}
+	}
+
+	return nil
 }
 }
 
 
 // CreateNetwork - creates a network in database
 // CreateNetwork - creates a network in database

+ 2 - 2
logic/nodes.go

@@ -147,7 +147,7 @@ func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node
 		defer nodeNetworkCacheMutex.Unlock()
 		defer nodeNetworkCacheMutex.Unlock()
 		return slices.Collect(maps.Values(networkNodes))
 		return slices.Collect(maps.Values(networkNodes))
 	}
 	}
-	var nodes = []models.Node{}
+	var nodes = make([]models.Node, 0, len(allNodes))
 	for i := range allNodes {
 	for i := range allNodes {
 		node := allNodes[i]
 		node := allNodes[i]
 		if node.Network == network {
 		if node.Network == network {
@@ -245,7 +245,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 		}
 		}
 	}
 	}
 
 
-	return fmt.Errorf("failed to update node " + currentNode.ID.String() + ", cannot change ID.")
+	return fmt.Errorf("failed to update node %s, cannot change ID", currentNode.ID.String())
 }
 }
 
 
 // DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node
 // DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node

+ 97 - 25
logic/peers.go

@@ -59,6 +59,80 @@ var (
 	}
 	}
 )
 )
 
 
+// GetHostPeerInfo - fetches required peer info per network
+func GetHostPeerInfo(host *models.Host) (models.HostPeerInfo, error) {
+	peerInfo := models.HostPeerInfo{
+		NetworkPeerIDs: make(map[models.NetworkID]models.PeerMap),
+	}
+	allNodes, err := GetAllNodes()
+	if err != nil {
+		return peerInfo, err
+	}
+	for _, nodeID := range host.Nodes {
+		nodeID := nodeID
+		node, err := GetNodeByID(nodeID)
+		if err != nil {
+			continue
+		}
+
+		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
+			continue
+		}
+		networkPeersInfo := make(models.PeerMap)
+		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+
+		currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
+		for _, peer := range currentPeers {
+			peer := peer
+			if peer.ID.String() == node.ID.String() {
+				logger.Log(2, "peer update, skipping self")
+				// skip yourself
+				continue
+			}
+
+			peerHost, err := GetHost(peer.HostID.String())
+			if err != nil {
+				logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
+				continue
+			}
+
+			var allowedToComm bool
+			if defaultDevicePolicy.Enabled {
+				allowedToComm = true
+			} else {
+				allowedToComm = IsPeerAllowed(node, peer, false)
+			}
+			if peer.Action != models.NODE_DELETE &&
+				!peer.PendingDelete &&
+				peer.Connected &&
+				nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
+				(defaultDevicePolicy.Enabled || allowedToComm) {
+
+				networkPeersInfo[peerHost.PublicKey.String()] = models.IDandAddr{
+					ID:         peer.ID.String(),
+					HostID:     peerHost.ID.String(),
+					Address:    peer.PrimaryAddress(),
+					Name:       peerHost.Name,
+					Network:    peer.Network,
+					ListenPort: peerHost.ListenPort,
+				}
+
+			}
+		}
+		var extPeerIDAndAddrs []models.IDandAddr
+		if node.IsIngressGateway {
+			_, extPeerIDAndAddrs, _, err = GetExtPeers(&node, &node)
+			if err == nil {
+				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+					networkPeersInfo[extPeerIdAndAddr.ID] = extPeerIdAndAddr
+				}
+			}
+		}
+		peerInfo.NetworkPeerIDs[models.NetworkID(node.Network)] = networkPeersInfo
+	}
+	return peerInfo, nil
+}
+
 // 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(network string, host *models.Host, allNodes []models.Node,
 func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.Node,
 	deletedNode *models.Node, deletedClients []models.ExtClient) (models.HostPeerUpdate, error) {
 	deletedNode *models.Node, deletedClients []models.ExtClient) (models.HostPeerUpdate, error) {
@@ -79,11 +153,11 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			IngressInfo: make(map[string]models.IngressInfo),
 			IngressInfo: make(map[string]models.IngressInfo),
 			AclRules:    make(map[string]models.AclRule),
 			AclRules:    make(map[string]models.AclRule),
 		},
 		},
-		PeerIDs:           make(models.PeerMap, 0),
-		Peers:             []wgtypes.PeerConfig{},
-		NodePeers:         []wgtypes.PeerConfig{},
-		HostNetworkInfo:   models.HostInfoMap{},
-		EndpointDetection: servercfg.IsEndpointDetectionEnabled(),
+		PeerIDs:         make(models.PeerMap, 0),
+		Peers:           []wgtypes.PeerConfig{},
+		NodePeers:       []wgtypes.PeerConfig{},
+		HostNetworkInfo: models.HostInfoMap{},
+		ServerConfig:    servercfg.ServerInfo,
 	}
 	}
 	defer func() {
 	defer func() {
 		if !hostPeerUpdate.FwUpdate.AllowAll {
 		if !hostPeerUpdate.FwUpdate.AllowAll {
@@ -229,21 +303,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
 			}
 			}
 			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
 			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
-			if servercfg.IsPro {
-				if (node.IsRelayed && node.RelayedBy != peer.ID.String()) ||
-					(peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer {
-					// if node is relayed and peer is not the relay, set remove to true
-					if _, ok := peerIndexMap[peerHost.PublicKey.String()]; ok {
-						continue
-					}
-					peerConfig.Remove = true
-					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
-					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
+			if (node.IsRelayed && node.RelayedBy != peer.ID.String()) ||
+				(peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer {
+				// if node is relayed and peer is not the relay, set remove to true
+				if _, ok := peerIndexMap[peerHost.PublicKey.String()]; ok {
 					continue
 					continue
 				}
 				}
-				if node.IsRelayed && node.RelayedBy == peer.ID.String() {
-					hostPeerUpdate = SetDefaultGwForRelayedUpdate(node, peer, hostPeerUpdate)
-				}
+				peerConfig.Remove = true
+				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
+				continue
+			}
+			if node.IsRelayed && node.RelayedBy == peer.ID.String() {
+				hostPeerUpdate = SetDefaultGwForRelayedUpdate(node, peer, hostPeerUpdate)
 			}
 			}
 
 
 			uselocal := false
 			uselocal := false
@@ -297,15 +369,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 			}
 			}
-			allowedips := GetAllowedIPs(&node, &peer, nil)
-			allowedToComm := IsPeerAllowed(node, peer, false)
+			var allowedToComm bool
+			if defaultDevicePolicy.Enabled {
+				allowedToComm = true
+			} else {
+				allowedToComm = IsPeerAllowed(node, peer, false)
+			}
 			if peer.Action != models.NODE_DELETE &&
 			if peer.Action != models.NODE_DELETE &&
 				!peer.PendingDelete &&
 				!peer.PendingDelete &&
 				peer.Connected &&
 				peer.Connected &&
 				nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
 				nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
 				(defaultDevicePolicy.Enabled || allowedToComm) &&
 				(defaultDevicePolicy.Enabled || allowedToComm) &&
 				(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
 				(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
-				peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
+				peerConfig.AllowedIPs = GetAllowedIPs(&node, &peer, nil) // only append allowed IPs if valid connection
 			}
 			}
 
 
 			var nodePeer wgtypes.PeerConfig
 			var nodePeer wgtypes.PeerConfig
@@ -466,10 +542,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			}
 			}
 		}
 		}
 	}
 	}
-
-	hostPeerUpdate.ManageDNS = servercfg.GetManageDNS()
-	hostPeerUpdate.Stun = servercfg.IsStunEnabled()
-	hostPeerUpdate.StunServers = servercfg.GetStunServers()
 	return hostPeerUpdate, nil
 	return hostPeerUpdate, nil
 }
 }
 
 

+ 225 - 13
logic/relay.go

@@ -1,34 +1,246 @@
 package logic
 package logic
 
 
 import (
 import (
+	"errors"
+	"fmt"
 	"net"
 	"net"
 
 
+	"github.com/google/uuid"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
 
 
-var GetRelays = func() ([]models.Node, error) {
-	return []models.Node{}, nil
+// GetRelays - gets all the nodes that are relays
+func GetRelays() ([]models.Node, error) {
+	nodes, err := GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	relays := make([]models.Node, 0)
+	for _, node := range nodes {
+		if node.IsRelay {
+			relays = append(relays, node)
+		}
+	}
+	return relays, nil
 }
 }
 
 
-var RelayedAllowedIPs = func(peer, node *models.Node) []net.IPNet {
-	return []net.IPNet{}
+// CreateRelay - creates a relay
+func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) {
+	var returnnodes []models.Node
+
+	node, err := GetNodeByID(relay.NodeID)
+	if err != nil {
+		return returnnodes, models.Node{}, err
+	}
+	host, err := GetHost(node.HostID.String())
+	if err != nil {
+		return returnnodes, models.Node{}, err
+	}
+	if host.OS != "linux" {
+		return returnnodes, models.Node{}, fmt.Errorf("only linux machines can be gateway nodes")
+	}
+	err = ValidateRelay(relay, false)
+	if err != nil {
+		return returnnodes, models.Node{}, err
+	}
+	node.IsRelay = true
+	node.IsGw = true
+	node.RelayedNodes = relay.RelayedNodes
+	node.SetLastModified()
+	err = UpsertNode(&node)
+	if err != nil {
+		return returnnodes, node, err
+	}
+	returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
+	return returnnodes, node, nil
+}
+
+// SetRelayedNodes- sets and saves node as relayed
+func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node {
+	var returnnodes []models.Node
+	for _, id := range relayed {
+		node, err := GetNodeByID(id)
+		if err != nil {
+			logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error())
+			continue
+		}
+		node.IsRelayed = setRelayed
+		if setRelayed {
+			node.RelayedBy = relay
+		} else {
+			node.RelayedBy = ""
+		}
+		node.SetLastModified()
+		if err := UpsertNode(&node); err != nil {
+			logger.Log(0, "setRelayedNodes.Insert", err.Error())
+			continue
+		}
+		returnnodes = append(returnnodes, node)
+	}
+	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
+func ValidateRelay(relay models.RelayRequest, update bool) error {
+	var err error
+
+	node, err := GetNodeByID(relay.NodeID)
+	if err != nil {
+		return err
+	}
+	if !update && node.IsRelay {
+		return errors.New("node is already acting as a relay")
+	}
+	for _, relayedNodeID := range relay.RelayedNodes {
+		relayedNode, err := GetNodeByID(relayedNodeID)
+		if err != nil {
+			return err
+		}
+		if relayedNode.IsIngressGateway {
+			return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")")
+		}
+		if relayedNode.IsInternetGateway {
+			return errors.New("cannot relay an internet gateway (" + relayedNodeID + ")")
+		}
+		if relayedNode.InternetGwID != "" && relayedNode.InternetGwID != relay.NodeID {
+			return errors.New("cannot relay an internet client (" + relayedNodeID + ")")
+		}
+		if relayedNode.IsFailOver {
+			return errors.New("cannot relay a failOver (" + relayedNodeID + ")")
+		}
+		if relayedNode.FailedOverBy != uuid.Nil {
+			ResetFailedOverPeer(&relayedNode)
+		}
+	}
+	return err
+}
+
+// UpdateRelayNodes - updates relay nodes
+func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []models.Node {
+	_ = SetRelayedNodes(false, relay, oldNodes)
+	return SetRelayedNodes(true, relay, newNodes)
+}
+
+func RelayUpdates(currentNode, newNode *models.Node) bool {
+	relayUpdates := false
+	if newNode.IsRelay {
+		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
+			relayUpdates = true
+		} else {
+			for i, node := range newNode.RelayedNodes {
+				if node != currentNode.RelayedNodes[i] {
+					relayUpdates = true
+				}
+			}
+		}
+	}
+	return relayUpdates
 }
 }
 
 
-var GetAllowedIpsForRelayed = func(relayed, relay *models.Node) []net.IPNet {
-	return []net.IPNet{}
+// UpdateRelayed - updates a relay's relayed nodes, and sends updates to the relayed nodes over MQ
+func UpdateRelayed(currentNode, newNode *models.Node) {
+	updatenodes := updateRelayNodes(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes)
+	if len(updatenodes) > 0 {
+		for _, relayedNode := range updatenodes {
+			node := relayedNode
+			ResetFailedOverPeer(&node)
+		}
+	}
 }
 }
 
 
-var UpdateRelayed = func(currentNode, newNode *models.Node) {
+// DeleteRelay - deletes a relay
+func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
+	var returnnodes []models.Node
+	node, err := GetNodeByID(nodeid)
+	if err != nil {
+		return returnnodes, models.Node{}, err
+	}
+	returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes)
+	node.IsRelay = false
+	node.RelayedNodes = []string{}
+	node.SetLastModified()
+	if err = UpsertNode(&node); err != nil {
+		return returnnodes, models.Node{}, err
+	}
+	return returnnodes, node, nil
 }
 }
 
 
-var SetRelayedNodes = func(setRelayed bool, relay string, relayed []string) []models.Node {
-	return []models.Node{}
+func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet {
+	var allowedIPs = []net.IPNet{}
+	for _, relayedNodeID := range peer.RelayedNodes {
+		if node.ID.String() == relayedNodeID {
+			continue
+		}
+		relayedNode, err := GetNodeByID(relayedNodeID)
+		if err != nil {
+			continue
+		}
+		allowed := getRelayedAddresses(relayedNodeID)
+		if relayedNode.IsEgressGateway {
+			allowed = append(allowed, GetEgressIPs(&relayedNode)...)
+		}
+		allowedIPs = append(allowedIPs, allowed...)
+	}
+	return allowedIPs
 }
 }
 
 
-var RelayUpdates = func(currentNode, newNode *models.Node) bool {
-	return false
+// GetAllowedIpsForRelayed - returns the peerConfig for a node relayed by relay
+func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNet) {
+	if relayed.RelayedBy != relay.ID.String() {
+		logger.Log(0, "RelayedByRelay called with invalid parameters")
+		return
+	}
+	if relay.InternetGwID != "" {
+		return GetAllowedIpForInetNodeClient(relayed, relay)
+	}
+	peers, err := GetNetworkNodes(relay.Network)
+	if err != nil {
+		logger.Log(0, "error getting network clients", err.Error())
+		return
+	}
+	for _, peer := range peers {
+		if peer.ID == relayed.ID || peer.ID == relay.ID {
+			continue
+		}
+		if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) {
+			allowedIPs = append(allowedIPs, GetAllowedIPs(relayed, &peer, nil)...)
+		}
+	}
+	return
 }
 }
 
 
-var ValidateRelay = func(relay models.RelayRequest, update bool) error {
-	return nil
+func getRelayedAddresses(id string) []net.IPNet {
+	addrs := []net.IPNet{}
+	node, err := GetNodeByID(id)
+	if err != nil {
+		logger.Log(0, "getRelayedAddresses: "+err.Error())
+		return addrs
+	}
+	if node.Address.IP != nil {
+		node.Address.Mask = net.CIDRMask(32, 32)
+		addrs = append(addrs, node.Address)
+	}
+	if node.Address6.IP != nil {
+		node.Address6.Mask = net.CIDRMask(128, 128)
+		addrs = append(addrs, node.Address6)
+	}
+	return addrs
 }
 }

+ 4 - 0
logic/status.go

@@ -18,6 +18,10 @@ func getNodeStatus(node *models.Node, t bool) {
 		node.Status = models.OnlineSt
 		node.Status = models.OnlineSt
 		return
 		return
 	}
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 		node.Status = models.OfflineSt
 		node.Status = models.OfflineSt
 		return
 		return

+ 1 - 1
logic/tags.go

@@ -259,7 +259,7 @@ func CheckIDSyntax(id string) error {
 	if len(id) < 3 {
 	if len(id) < 3 {
 		return errors.New("name should have min 3 characters")
 		return errors.New("name should have min 3 characters")
 	}
 	}
-	reg, err := regexp.Compile("^[a-zA-Z-]+$")
+	reg, err := regexp.Compile("^[a-zA-Z0-9- ]+$")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 25 - 0
logic/util.go

@@ -7,6 +7,7 @@ import (
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"log/slog"
 	"net"
 	"net"
 	"os"
 	"os"
 	"strings"
 	"strings"
@@ -91,6 +92,30 @@ func StringSliceContains(slice []string, item string) bool {
 	}
 	}
 	return false
 	return false
 }
 }
+func SetVerbosity(logLevel int) {
+	var level slog.Level
+	switch logLevel {
+
+	case 0:
+		level = slog.LevelInfo
+	case 1:
+		level = slog.LevelError
+	case 2:
+		level = slog.LevelWarn
+	case 3:
+		level = slog.LevelDebug
+
+	default:
+		level = slog.LevelInfo
+	}
+	// Create the logger with the chosen level
+	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+		Level: level,
+	})
+	logger := slog.New(handler)
+	slog.SetDefault(logger)
+
+}
 
 
 // NormalizeCIDR - returns the first address of CIDR
 // NormalizeCIDR - returns the first address of CIDR
 func NormalizeCIDR(address string) (string, error) {
 func NormalizeCIDR(address string) (string, error) {

+ 16 - 0
migrate/migrate.go

@@ -28,6 +28,7 @@ func Run() {
 	updateHosts()
 	updateHosts()
 	updateNodes()
 	updateNodes()
 	updateAcls()
 	updateAcls()
+	migrateToGws()
 }
 }
 
 
 func assignSuperAdmin() {
 func assignSuperAdmin() {
@@ -441,3 +442,18 @@ func createDefaultTagsAndPolicies() {
 	}
 	}
 	logic.MigrateAclPolicies()
 	logic.MigrateAclPolicies()
 }
 }
+
+func migrateToGws() {
+	nodes, err := logic.GetAllNodes()
+	if err != nil {
+		return
+	}
+	for _, node := range nodes {
+		if node.IsIngressGateway || node.IsRelay {
+			node.IsGw = true
+			node.IsIngressGateway = true
+			node.IsRelay = true
+			logic.UpsertNode(&node)
+		}
+	}
+}

+ 9 - 0
models/gateway.go

@@ -0,0 +1,9 @@
+package models
+
+type CreateGwReq struct {
+	IngressRequest
+	RelayRequest
+}
+
+type DeleteGw struct {
+}

+ 2 - 0
models/host.go

@@ -98,6 +98,8 @@ type HostMqAction string
 const (
 const (
 	// Upgrade - const to request host to update it's client
 	// Upgrade - const to request host to update it's client
 	Upgrade HostMqAction = "UPGRADE"
 	Upgrade HostMqAction = "UPGRADE"
+	// ForceUpgrade - const for forcing a host to upgrade its client binary
+	ForceUpgrade HostMqAction = "FORCE_UPGRADE"
 	// SignalHost - const for host signal action
 	// SignalHost - const for host signal action
 	SignalHost HostMqAction = "SIGNAL_HOST"
 	SignalHost HostMqAction = "SIGNAL_HOST"
 	// UpdateHost - constant for host update action
 	// UpdateHost - constant for host update action

+ 1 - 0
models/metrics.go

@@ -48,6 +48,7 @@ type HostNetworkInfo struct {
 	ListenPort   int     `json:"listen_port" yaml:"listen_port"`
 	ListenPort   int     `json:"listen_port" yaml:"listen_port"`
 	IsStaticPort bool    `json:"is_static_port"`
 	IsStaticPort bool    `json:"is_static_port"`
 	IsStatic     bool    `json:"is_static"`
 	IsStatic     bool    `json:"is_static"`
+	Version      string  `json:"version"`
 }
 }
 
 
 // PeerMap - peer map for ids and addresses in metrics
 // PeerMap - peer map for ids and addresses in metrics

+ 26 - 18
models/mqtt.go

@@ -6,27 +6,35 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
+type HostPeerInfo struct {
+	NetworkPeerIDs map[NetworkID]PeerMap `json:"network_peers"`
+}
+
 // 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"`
-	ChangeDefaultGw   bool                 `json:"change_default_gw"`
-	DefaultGwIp       net.IP               `json:"default_gw_ip"`
-	IsInternetGw      bool                 `json:"is_inet_gw"`
-	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"`
-	ServerAddrs       []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	Host            Host                  `json:"host"`
+	ChangeDefaultGw bool                  `json:"change_default_gw"`
+	DefaultGwIp     net.IP                `json:"default_gw_ip"`
+	IsInternetGw    bool                  `json:"is_inet_gw"`
+	NodeAddrs       []net.IPNet           `json:"nodes_addrs"`
+	Server          string                `json:"server"`
+	ServerVersion   string                `json:"serverversion"`
+	ServerAddrs     []ServerAddr          `json:"serveraddrs"`
+	NodePeers       []wgtypes.PeerConfig  `json:"node_peers"`
+	Peers           []wgtypes.PeerConfig  `json:"host_peers"`
+	PeerIDs         PeerMap               `json:"peerids"`
+	HostNetworkInfo HostInfoMap           `json:"host_network_info,omitempty"`
+	EgressRoutes    []EgressNetworkRoutes `json:"egress_network_routes"`
+	FwUpdate        FwUpdate              `json:"fw_update"`
+	ReplacePeers    bool                  `json:"replace_peers"`
+	ServerConfig
+	OldPeerUpdateFields
+}
+
+type OldPeerUpdateFields struct {
 	NodePeers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
 	NodePeers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
-	Peers             []wgtypes.PeerConfig
-	PeerIDs           PeerMap               `json:"peerids" bson:"peerids" yaml:"peerids"`
-	HostNetworkInfo   HostInfoMap           `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
-	EgressRoutes      []EgressNetworkRoutes `json:"egress_network_routes"`
-	FwUpdate          FwUpdate              `json:"fw_update"`
-	ReplacePeers      bool                  `json:"replace_peers"`
-	EndpointDetection bool                  `json:"endpoint_detection"`
-	ManageDNS         bool                  `yaml:"manage_dns"`
-	Stun              bool                  `yaml:"stun"`
-	StunServers       string                `yaml:"stun_servers"`
+	OldPeers          []wgtypes.PeerConfig `json:"Peers"`
+	EndpointDetection bool                 `json:"endpoint_detection"`
 }
 }
 
 
 type FwRule struct {
 type FwRule struct {

+ 12 - 1
models/network.go

@@ -42,9 +42,10 @@ func (network *Network) SetNetworkLastModified() {
 }
 }
 
 
 // Network.SetDefaults - sets default values for a network struct
 // Network.SetDefaults - sets default values for a network struct
-func (network *Network) SetDefaults() {
+func (network *Network) SetDefaults() (upsert bool) {
 	if network.DefaultUDPHolePunch == "" {
 	if network.DefaultUDPHolePunch == "" {
 		network.DefaultUDPHolePunch = "no"
 		network.DefaultUDPHolePunch = "no"
+		upsert = true
 	}
 	}
 	if network.DefaultInterface == "" {
 	if network.DefaultInterface == "" {
 		if len(network.NetID) < 33 {
 		if len(network.NetID) < 33 {
@@ -52,35 +53,45 @@ func (network *Network) SetDefaults() {
 		} else {
 		} else {
 			network.DefaultInterface = network.NetID
 			network.DefaultInterface = network.NetID
 		}
 		}
+		upsert = true
 	}
 	}
 	if network.DefaultListenPort == 0 {
 	if network.DefaultListenPort == 0 {
 		network.DefaultListenPort = 51821
 		network.DefaultListenPort = 51821
+		upsert = true
 	}
 	}
 	if network.NodeLimit == 0 {
 	if network.NodeLimit == 0 {
 		network.NodeLimit = 999999999
 		network.NodeLimit = 999999999
+		upsert = true
 	}
 	}
 	if network.DefaultKeepalive == 0 {
 	if network.DefaultKeepalive == 0 {
 		network.DefaultKeepalive = 20
 		network.DefaultKeepalive = 20
+		upsert = true
 	}
 	}
 	if network.AllowManualSignUp == "" {
 	if network.AllowManualSignUp == "" {
 		network.AllowManualSignUp = "no"
 		network.AllowManualSignUp = "no"
+		upsert = true
 	}
 	}
 
 
 	if network.IsIPv4 == "" {
 	if network.IsIPv4 == "" {
 		network.IsIPv4 = "yes"
 		network.IsIPv4 = "yes"
+		upsert = true
 	}
 	}
 
 
 	if network.IsIPv6 == "" {
 	if network.IsIPv6 == "" {
 		network.IsIPv6 = "no"
 		network.IsIPv6 = "no"
+		upsert = true
 	}
 	}
 
 
 	if network.DefaultMTU == 0 {
 	if network.DefaultMTU == 0 {
 		network.DefaultMTU = 1280
 		network.DefaultMTU = 1280
+		upsert = true
 	}
 	}
 
 
 	if network.DefaultACL == "" {
 	if network.DefaultACL == "" {
 		network.DefaultACL = "yes"
 		network.DefaultACL = "yes"
+		upsert = true
 	}
 	}
+	return
 }
 }
 
 
 func (network *Network) GetNetworkNetworkCIDR4() *net.IPNet {
 func (network *Network) GetNetworkNetworkCIDR4() *net.IPNet {

+ 11 - 9
models/node.go

@@ -15,11 +15,12 @@ import (
 type NodeStatus string
 type NodeStatus string
 
 
 const (
 const (
-	OnlineSt  NodeStatus = "online"
-	OfflineSt NodeStatus = "offline"
-	WarningSt NodeStatus = "warning"
-	ErrorSt   NodeStatus = "error"
-	UnKnown   NodeStatus = "unknown"
+	OnlineSt     NodeStatus = "online"
+	OfflineSt    NodeStatus = "offline"
+	WarningSt    NodeStatus = "warning"
+	ErrorSt      NodeStatus = "error"
+	UnKnown      NodeStatus = "unknown"
+	Disconnected NodeStatus = "disconnected"
 )
 )
 
 
 // LastCheckInThreshold - if node's checkin more than this threshold,then node is declared as offline
 // LastCheckInThreshold - if node's checkin more than this threshold,then node is declared as offline
@@ -78,11 +79,12 @@ type CommonNode struct {
 	Action              string    `json:"action"              yaml:"action"`
 	Action              string    `json:"action"              yaml:"action"`
 	LocalAddress        net.IPNet `json:"localaddress"        yaml:"localaddress"`
 	LocalAddress        net.IPNet `json:"localaddress"        yaml:"localaddress"`
 	IsEgressGateway     bool      `json:"isegressgateway"     yaml:"isegressgateway"`
 	IsEgressGateway     bool      `json:"isegressgateway"     yaml:"isegressgateway"`
-	EgressGatewayRanges []string  `json:"egressgatewayranges" yaml:"egressgatewayranges"                                 bson:"egressgatewayranges"`
+	EgressGatewayRanges []string  `json:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool      `json:"isingressgateway"    yaml:"isingressgateway"`
 	IsIngressGateway    bool      `json:"isingressgateway"    yaml:"isingressgateway"`
-	IsRelayed           bool      `json:"isrelayed"           yaml:"isrelayed"                                           bson:"isrelayed"`
-	RelayedBy           string    `json:"relayedby"           yaml:"relayedby"                                           bson:"relayedby"`
-	IsRelay             bool      `json:"isrelay"             yaml:"isrelay"                                             bson:"isrelay"`
+	IsRelayed           bool      `json:"isrelayed"           yaml:"isrelayed"`
+	RelayedBy           string    `json:"relayedby"           yaml:"relayedby"`
+	IsRelay             bool      `json:"isrelay"             yaml:"isrelay"`
+	IsGw                bool      `json:"is_gw"             yaml:"is_gw"`
 	RelayedNodes        []string  `json:"relaynodes"          yaml:"relayedNodes"`
 	RelayedNodes        []string  `json:"relaynodes"          yaml:"relayedNodes"`
 	IngressDNS          string    `json:"ingressdns"          yaml:"ingressdns"`
 	IngressDNS          string    `json:"ingressdns"          yaml:"ingressdns"`
 	DNSOn               bool      `json:"dnson"               yaml:"dnson"`
 	DNSOn               bool      `json:"dnson"               yaml:"dnson"`

+ 20 - 18
models/structs.go

@@ -252,24 +252,26 @@ type NodeJoinResponse struct {
 
 
 // ServerConfig - struct for dealing with the server information for a netclient
 // ServerConfig - struct for dealing with the server information for a netclient
 type ServerConfig struct {
 type ServerConfig struct {
-	CoreDNSAddr    string `yaml:"corednsaddr"`
-	API            string `yaml:"api"`
-	APIPort        string `yaml:"apiport"`
-	DNSMode        string `yaml:"dnsmode"`
-	Version        string `yaml:"version"`
-	MQPort         string `yaml:"mqport"`
-	MQUserName     string `yaml:"mq_username"`
-	MQPassword     string `yaml:"mq_password"`
-	BrokerType     string `yaml:"broker_type"`
-	Server         string `yaml:"server"`
-	Broker         string `yaml:"broker"`
-	IsPro          bool   `yaml:"isee" json:"Is_EE"`
-	TrafficKey     []byte `yaml:"traffickey"`
-	MetricInterval string `yaml:"metric_interval"`
-	ManageDNS      bool   `yaml:"manage_dns"`
-	Stun           bool   `yaml:"stun"`
-	StunServers    string `yaml:"stun_servers"`
-	DefaultDomain  string `yaml:"default_domain"`
+	CoreDNSAddr       string `yaml:"corednsaddr"`
+	API               string `yaml:"api"`
+	APIPort           string `yaml:"apiport"`
+	DNSMode           string `yaml:"dnsmode"`
+	Version           string `yaml:"version"`
+	MQPort            string `yaml:"mqport"`
+	MQUserName        string `yaml:"mq_username"`
+	MQPassword        string `yaml:"mq_password"`
+	BrokerType        string `yaml:"broker_type"`
+	Server            string `yaml:"server"`
+	Broker            string `yaml:"broker"`
+	IsPro             bool   `yaml:"isee" json:"Is_EE"`
+	TrafficKey        []byte `yaml:"traffickey"`
+	MetricInterval    string `yaml:"metric_interval"`
+	MetricsPort       int    `yaml:"metrics_port"`
+	ManageDNS         bool   `yaml:"manage_dns"`
+	Stun              bool   `yaml:"stun"`
+	StunServers       string `yaml:"stun_servers"`
+	EndpointDetection bool   `yaml:"endpoint_detection"`
+	DefaultDomain     string `yaml:"default_domain"`
 }
 }
 
 
 // User.NameInCharset - returns if name is in charset below or not
 // User.NameInCharset - returns if name is in charset below or not

+ 1 - 0
mq/handlers.go

@@ -280,6 +280,7 @@ func HandleHostCheckin(h, currentHost *models.Host) bool {
 		(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) ||
 		(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) ||
 		(h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort) || (!h.EndpointIPv6.Equal(currentHost.EndpointIPv6))
 		(h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort) || (!h.EndpointIPv6.Equal(currentHost.EndpointIPv6))
 	if ifaceDelta { // only save if something changes
 	if ifaceDelta { // only save if something changes
+
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIPv6 = h.EndpointIPv6
 		currentHost.EndpointIPv6 = h.EndpointIPv6
 		currentHost.Interfaces = h.Interfaces
 		currentHost.Interfaces = h.Interfaces

+ 5 - 1
mq/publishers.go

@@ -17,7 +17,6 @@ import (
 
 
 // PublishPeerUpdate --- determines and publishes a peer update to all the hosts
 // PublishPeerUpdate --- determines and publishes a peer update to all the hosts
 func PublishPeerUpdate(replacePeers bool) error {
 func PublishPeerUpdate(replacePeers bool) error {
-
 	if !servercfg.IsMessageQueueBackend() {
 	if !servercfg.IsMessageQueueBackend() {
 		return nil
 		return nil
 	}
 	}
@@ -114,6 +113,11 @@ func PublishSingleHostPeerUpdate(host *models.Host, allNodes []models.Node, dele
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	peerUpdate.OldPeerUpdateFields = models.OldPeerUpdateFields{
+		NodePeers:         peerUpdate.NodePeers,
+		OldPeers:          peerUpdate.Peers,
+		EndpointDetection: peerUpdate.ServerConfig.EndpointDetection,
+	}
 	peerUpdate.ReplacePeers = replacePeers
 	peerUpdate.ReplacePeers = replacePeers
 	data, err := json.Marshal(&peerUpdate)
 	data, err := json.Marshal(&peerUpdate)
 	if err != nil {
 	if err != nil {

+ 137 - 4
pro/controllers/failover.go

@@ -19,7 +19,7 @@ import (
 
 
 // FailOverHandlers - handlers for FailOver
 // FailOverHandlers - handlers for FailOver
 func FailOverHandlers(r *mux.Router) {
 func FailOverHandlers(r *mux.Router) {
-	r.HandleFunc("/api/v1/node/{nodeid}/failover", http.HandlerFunc(getfailOver)).
+	r.HandleFunc("/api/v1/node/{nodeid}/failover", controller.Authorize(true, false, "host", http.HandlerFunc(getfailOver))).
 		Methods(http.MethodGet)
 		Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))).
 	r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))).
 		Methods(http.MethodPost)
 		Methods(http.MethodPost)
@@ -29,6 +29,8 @@ func FailOverHandlers(r *mux.Router) {
 		Methods(http.MethodPost)
 		Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).
 	r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).
 		Methods(http.MethodPost)
 		Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/node/{nodeid}/failover_check", controller.Authorize(true, false, "host", http.HandlerFunc(checkfailOverCtx))).
+		Methods(http.MethodGet)
 }
 }
 
 
 // @Summary     Get failover node
 // @Summary     Get failover node
@@ -44,7 +46,6 @@ func getfailOver(w http.ResponseWriter, r *http.Request) {
 	// confirm host exists
 	// confirm host exists
 	node, err := logic.GetNodeByID(nodeid)
 	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
 	if err != nil {
-		slog.Error("failed to get node:", "node", nodeid, "error", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
@@ -140,6 +141,7 @@ func deletefailOver(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+	proLogic.RemoveFailOverFromCache(node.Network)
 	go func() {
 	go func() {
 		proLogic.ResetFailOver(&node)
 		proLogic.ResetFailOver(&node)
 		mq.PublishPeerUpdate(false)
 		mq.PublishPeerUpdate(false)
@@ -265,10 +267,9 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 		)
 		)
 		return
 		return
 	}
 	}
-
 	err = proLogic.SetFailOverCtx(failOverNode, node, peerNode)
 	err = proLogic.SetFailOverCtx(failOverNode, node, peerNode)
 	if err != nil {
 	if err != nil {
-		slog.Error("failed to create failover", "id", node.ID.String(),
+		slog.Debug("failed to create failover", "id", node.ID.String(),
 			"network", node.Network, "error", err)
 			"network", node.Network, "error", err)
 		logic.ReturnErrorResponse(
 		logic.ReturnErrorResponse(
 			w,
 			w,
@@ -293,3 +294,135 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	logic.ReturnSuccessResponse(w, r, "relayed successfully")
 	logic.ReturnSuccessResponse(w, r, "relayed successfully")
 }
 }
+
+// @Summary     checkfailOverCtx
+// @Router      /api/v1/node/{nodeid}/failover_check [get]
+// @Tags        PRO
+// @Param       nodeid path string true "Node ID"
+// @Accept      json
+// @Param       body body models.FailOverMeReq true "Failover request"
+// @Success     200 {object} models.SuccessResponse
+// @Failure     400 {object} models.ErrorResponse
+// @Failure     500 {object} models.ErrorResponse
+func checkfailOverCtx(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	nodeid := params["nodeid"]
+	// confirm host exists
+	node, err := logic.GetNodeByID(nodeid)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to get node:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	failOverNode, exists := proLogic.FailOverExists(node.Network)
+	if !exists {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(
+				fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name),
+				"badrequest",
+			),
+		)
+		return
+	}
+	var failOverReq models.FailOverMeReq
+	err = json.NewDecoder(r.Body).Decode(&failOverReq)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	peerNode, err := logic.GetNodeByID(failOverReq.NodeID)
+	if err != nil {
+		slog.Error("peer not found: ", "nodeid", failOverReq.NodeID, "error", err)
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("peer not found"), "badrequest"),
+		)
+		return
+	}
+	if peerNode.IsFailOver {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
+		)
+		return
+	}
+	if node.IsFailOver {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("node is acting as failover"), "badrequest"),
+		)
+		return
+	}
+	if peerNode.IsFailOver {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("peer is acting as failover"), "badrequest"),
+		)
+		return
+	}
+	if node.IsRelayed && node.RelayedBy == peerNode.ID.String() {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"),
+		)
+		return
+	}
+	if node.IsRelay && peerNode.RelayedBy == node.ID.String() {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"),
+		)
+		return
+	}
+	if node.IsInternetGateway && peerNode.InternetGwID == node.ID.String() {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(
+				errors.New("node acting as internet gw for the peer node"),
+				"badrequest",
+			),
+		)
+		return
+	}
+	if node.InternetGwID != "" && node.InternetGwID == peerNode.ID.String() {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(
+				errors.New("node using a internet gw by the peer node"),
+				"badrequest",
+			),
+		)
+		return
+	}
+
+	err = proLogic.CheckFailOverCtx(failOverNode, node, peerNode)
+	if err != nil {
+		slog.Error("failover ctx cannot be set ", "error", err)
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(fmt.Errorf("failover ctx cannot be set: %v", err), "internal"),
+		)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	logic.ReturnSuccessResponse(w, r, "failover can be set")
+}

+ 3 - 0
pro/controllers/inet_gws.go

@@ -84,6 +84,9 @@ func createInternetGw(w http.ResponseWriter, r *http.Request) {
 			}()
 			}()
 		}
 		}
 	}
 	}
+	if node.IsGw && node.IngressDNS == "" {
+		node.IngressDNS = "1.1.1.1"
+	}
 	err = logic.UpsertNode(&node)
 	err = logic.UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

+ 0 - 152
pro/controllers/relay.go

@@ -1,152 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/google/uuid"
-	proLogic "github.com/gravitl/netmaker/pro/logic"
-
-	"github.com/gorilla/mux"
-	controller "github.com/gravitl/netmaker/controllers"
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/mq"
-)
-
-// RelayHandlers - handle Pro Relays
-func RelayHandlers(r *mux.Router) {
-
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createRelay))).Methods(http.MethodPost)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/v1/host/{hostid}/failoverme", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).Methods(http.MethodPost)
-}
-
-// @Summary     Create a relay
-// @Router      /api/nodes/{network}/{nodeid}/createrelay [post]
-// @Tags        PRO
-// @Accept      json
-// @Produce     json
-// @Param       network path string true "Network ID"
-// @Param       nodeid path string true "Node ID"
-// @Param       body body models.RelayRequest true "Relay request parameters"
-// @Success     200 {object} models.ApiNode
-// @Failure     400 {object} models.ErrorResponse
-// @Failure     500 {object} models.ErrorResponse
-func createRelay(w http.ResponseWriter, r *http.Request) {
-	var relayRequest models.RelayRequest
-	var params = mux.Vars(r)
-	w.Header().Set("Content-Type", "application/json")
-	err := json.NewDecoder(r.Body).Decode(&relayRequest)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	relayRequest.NetID = params["network"]
-	relayRequest.NodeID = params["nodeid"]
-	_, relayNode, err := proLogic.CreateRelay(relayRequest)
-	if err != nil {
-		logger.Log(
-			0,
-			r.Header.Get("user"),
-			fmt.Sprintf(
-				"failed to create relay on node [%s] on network [%s]: %v",
-				relayRequest.NodeID,
-				relayRequest.NetID,
-				err,
-			),
-		)
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	for _, relayedNodeID := range relayNode.RelayedNodes {
-		relayedNode, err := logic.GetNodeByID(relayedNodeID)
-		if err == nil {
-			if relayedNode.FailedOverBy != uuid.Nil {
-				go logic.ResetFailedOverPeer(&relayedNode)
-			}
-
-		}
-	}
-	go mq.PublishPeerUpdate(false)
-	logger.Log(
-		1,
-		r.Header.Get("user"),
-		"created relay on node",
-		relayRequest.NodeID,
-		"on network",
-		relayRequest.NetID,
-	)
-	apiNode := relayNode.ConvertToAPINode()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-}
-
-// @Summary     Remove a relay
-// @Router      /api/nodes/{network}/{nodeid}/deleterelay [delete]
-// @Tags        PRO
-// @Accept      json
-// @Produce     json
-// @Param       network path string true "Network ID"
-// @Param       nodeid path string true "Node ID"
-// @Success     200 {object} models.ApiNode
-// @Failure     400 {object} models.ErrorResponse
-// @Failure     500 {object} models.ErrorResponse
-func deleteRelay(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/json")
-	var params = mux.Vars(r)
-	nodeid := params["nodeid"]
-	netid := params["network"]
-	updateNodes, node, err := proLogic.DeleteRelay(netid, nodeid)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-	logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid)
-	go func() {
-		for _, relayedNode := range updateNodes {
-			err = mq.NodeUpdate(&relayedNode)
-			if err != nil {
-				logger.Log(
-					1,
-					"relayed node update ",
-					relayedNode.ID.String(),
-					"on network",
-					relayedNode.Network,
-					": ",
-					err.Error(),
-				)
-
-			}
-			h, err := logic.GetHost(relayedNode.HostID.String())
-			if err == nil {
-				if h.OS == models.OS_Types.IoT {
-					nodes, err := logic.GetAllNodes()
-					if err != nil {
-						return
-					}
-					node.IsRelay = true // for iot update to recognise that it has to delete relay peer
-					if err = mq.PublishSingleHostPeerUpdate(h, nodes, &node, nil, false, nil); err != nil {
-						logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error())
-					}
-				}
-			}
-		}
-		mq.PublishPeerUpdate(false)
-	}()
-	logger.Log(
-		1,
-		r.Header.Get("user"),
-		"deleted relay on node",
-		node.ID.String(),
-		"on network",
-		node.Network,
-	)
-	apiNode := node.ConvertToAPINode()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-}

+ 1 - 8
pro/initialize.go

@@ -29,7 +29,6 @@ func InitPro() {
 	controller.HttpHandlers = append(
 	controller.HttpHandlers = append(
 		controller.HttpHandlers,
 		controller.HttpHandlers,
 		proControllers.MetricHandlers,
 		proControllers.MetricHandlers,
-		proControllers.RelayHandlers,
 		proControllers.UserHandlers,
 		proControllers.UserHandlers,
 		proControllers.FailOverHandlers,
 		proControllers.FailOverHandlers,
 		proControllers.InetHandlers,
 		proControllers.InetHandlers,
@@ -91,6 +90,7 @@ func InitPro() {
 			slog.Error("no OAuth provider found or not configured, continuing without OAuth")
 			slog.Error("no OAuth provider found or not configured, continuing without OAuth")
 		}
 		}
 		proLogic.LoadNodeMetricsToCache()
 		proLogic.LoadNodeMetricsToCache()
+		proLogic.InitFailOverCache()
 	})
 	})
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
@@ -106,13 +106,6 @@ func InitPro() {
 	logic.GetMetrics = proLogic.GetMetrics
 	logic.GetMetrics = proLogic.GetMetrics
 	logic.UpdateMetrics = proLogic.UpdateMetrics
 	logic.UpdateMetrics = proLogic.UpdateMetrics
 	logic.DeleteMetrics = proLogic.DeleteMetrics
 	logic.DeleteMetrics = proLogic.DeleteMetrics
-	logic.GetRelays = proLogic.GetRelays
-	logic.GetAllowedIpsForRelayed = proLogic.GetAllowedIpsForRelayed
-	logic.RelayedAllowedIPs = proLogic.RelayedAllowedIPs
-	logic.UpdateRelayed = proLogic.UpdateRelayed
-	logic.SetRelayedNodes = proLogic.SetRelayedNodes
-	logic.RelayUpdates = proLogic.RelayUpdates
-	logic.ValidateRelay = proLogic.ValidateRelay
 	logic.GetTrialEndDate = getTrialEndDate
 	logic.GetTrialEndDate = getTrialEndDate
 	logic.SetDefaultGw = proLogic.SetDefaultGw
 	logic.SetDefaultGw = proLogic.SetDefaultGw
 	logic.SetDefaultGwForRelayedUpdate = proLogic.SetDefaultGwForRelayedUpdate
 	logic.SetDefaultGwForRelayedUpdate = proLogic.SetDefaultGwForRelayedUpdate

+ 67 - 12
pro/logic/failover.go

@@ -13,7 +13,49 @@ import (
 )
 )
 
 
 var failOverCtxMutex = &sync.RWMutex{}
 var failOverCtxMutex = &sync.RWMutex{}
+var failOverCacheMutex = &sync.RWMutex{}
+var failOverCache = make(map[models.NetworkID]string)
 
 
+func InitFailOverCache() {
+	failOverCacheMutex.Lock()
+	defer failOverCacheMutex.Unlock()
+	networks, err := logic.GetNetworks()
+	if err != nil {
+		return
+	}
+	allNodes, err := logic.GetAllNodes()
+	if err != nil {
+		return
+	}
+
+	for _, network := range networks {
+		networkNodes := logic.GetNetworkNodesMemory(allNodes, network.NetID)
+		for _, node := range networkNodes {
+			if node.IsFailOver {
+				failOverCache[models.NetworkID(network.NetID)] = node.ID.String()
+				break
+			}
+		}
+	}
+}
+
+func CheckFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
+	failOverCtxMutex.RLock()
+	defer failOverCtxMutex.RUnlock()
+	if peerNode.FailOverPeers == nil {
+		return nil
+	}
+	if victimNode.FailOverPeers == nil {
+		return nil
+	}
+	_, peerHasFailovered := peerNode.FailOverPeers[victimNode.ID.String()]
+	_, victimHasFailovered := victimNode.FailOverPeers[peerNode.ID.String()]
+	if peerHasFailovered && victimHasFailovered &&
+		victimNode.FailedOverBy == failOverNode.ID && peerNode.FailedOverBy == failOverNode.ID {
+		return errors.New("failover ctx is already set")
+	}
+	return nil
+}
 func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
 func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
 	failOverCtxMutex.Lock()
 	failOverCtxMutex.Lock()
 	defer failOverCtxMutex.Unlock()
 	defer failOverCtxMutex.Unlock()
@@ -23,13 +65,16 @@ func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
 	if victimNode.FailOverPeers == nil {
 	if victimNode.FailOverPeers == nil {
 		victimNode.FailOverPeers = make(map[string]struct{})
 		victimNode.FailOverPeers = make(map[string]struct{})
 	}
 	}
+	_, peerHasFailovered := peerNode.FailOverPeers[victimNode.ID.String()]
+	_, victimHasFailovered := victimNode.FailOverPeers[peerNode.ID.String()]
+	if peerHasFailovered && victimHasFailovered &&
+		victimNode.FailedOverBy == failOverNode.ID && peerNode.FailedOverBy == failOverNode.ID {
+		return errors.New("failover ctx is already set")
+	}
 	peerNode.FailOverPeers[victimNode.ID.String()] = struct{}{}
 	peerNode.FailOverPeers[victimNode.ID.String()] = struct{}{}
 	victimNode.FailOverPeers[peerNode.ID.String()] = struct{}{}
 	victimNode.FailOverPeers[peerNode.ID.String()] = struct{}{}
 	victimNode.FailedOverBy = failOverNode.ID
 	victimNode.FailedOverBy = failOverNode.ID
 	peerNode.FailedOverBy = failOverNode.ID
 	peerNode.FailedOverBy = failOverNode.ID
-	if err := logic.UpsertNode(&failOverNode); err != nil {
-		return err
-	}
 	if err := logic.UpsertNode(&victimNode); err != nil {
 	if err := logic.UpsertNode(&victimNode); err != nil {
 		return err
 		return err
 	}
 	}
@@ -50,17 +95,26 @@ func GetFailOverNode(network string, allNodes []models.Node) (models.Node, error
 	return models.Node{}, errors.New("auto relay not found")
 	return models.Node{}, errors.New("auto relay not found")
 }
 }
 
 
+func RemoveFailOverFromCache(network string) {
+	failOverCacheMutex.Lock()
+	defer failOverCacheMutex.Unlock()
+	delete(failOverCache, models.NetworkID(network))
+}
+
+func SetFailOverInCache(node models.Node) {
+	failOverCacheMutex.Lock()
+	defer failOverCacheMutex.Unlock()
+	failOverCache[models.NetworkID(node.Network)] = node.ID.String()
+}
+
 // FailOverExists - checks if failOver exists already in the network
 // FailOverExists - checks if failOver exists already in the network
 func FailOverExists(network string) (failOverNode models.Node, exists bool) {
 func FailOverExists(network string) (failOverNode models.Node, exists bool) {
-	nodes, err := logic.GetNetworkNodes(network)
-	if err != nil {
-		return
-	}
-	for _, node := range nodes {
-		if node.IsFailOver {
-			exists = true
-			failOverNode = node
-			return
+	failOverCacheMutex.RLock()
+	defer failOverCacheMutex.RUnlock()
+	if nodeID, ok := failOverCache[models.NetworkID(network)]; ok {
+		failOverNode, err := logic.GetNodeByID(nodeID)
+		if err == nil {
+			return failOverNode, true
 		}
 		}
 	}
 	}
 	return
 	return
@@ -185,5 +239,6 @@ func CreateFailOver(node models.Node) error {
 		slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
 		slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
 		return err
 		return err
 	}
 	}
+	SetFailOverInCache(node)
 	return nil
 	return nil
 }
 }

+ 0 - 255
pro/logic/relays.go

@@ -1,255 +0,0 @@
-package logic
-
-import (
-	"errors"
-	"fmt"
-	"net"
-
-	"github.com/google/uuid"
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/logic/acls/nodeacls"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/mq"
-	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/exp/slog"
-)
-
-// GetRelays - gets all the nodes that are relays
-func GetRelays() ([]models.Node, error) {
-	nodes, err := logic.GetAllNodes()
-	if err != nil {
-		return nil, err
-	}
-	relays := make([]models.Node, 0)
-	for _, node := range nodes {
-		if node.IsRelay {
-			relays = append(relays, node)
-		}
-	}
-	return relays, nil
-}
-
-// CreateRelay - creates a relay
-func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) {
-	var returnnodes []models.Node
-
-	node, err := logic.GetNodeByID(relay.NodeID)
-	if err != nil {
-		return returnnodes, models.Node{}, err
-	}
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return returnnodes, models.Node{}, err
-	}
-	if host.OS != "linux" {
-		return returnnodes, models.Node{}, fmt.Errorf("only linux machines can be relay nodes")
-	}
-	err = ValidateRelay(relay, false)
-	if err != nil {
-		return returnnodes, models.Node{}, err
-	}
-	node.IsRelay = true
-	node.RelayedNodes = relay.RelayedNodes
-	node.SetLastModified()
-	err = logic.UpsertNode(&node)
-	if err != nil {
-		return returnnodes, node, err
-	}
-	returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
-	return returnnodes, node, nil
-}
-
-// SetRelayedNodes- sets and saves node as relayed
-func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node {
-	var returnnodes []models.Node
-	for _, id := range relayed {
-		node, err := logic.GetNodeByID(id)
-		if err != nil {
-			logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error())
-			continue
-		}
-		node.IsRelayed = setRelayed
-		if setRelayed {
-			node.RelayedBy = relay
-		} else {
-			node.RelayedBy = ""
-		}
-		node.SetLastModified()
-		if err := logic.UpsertNode(&node); err != nil {
-			logger.Log(0, "setRelayedNodes.Insert", err.Error())
-			continue
-		}
-		returnnodes = append(returnnodes, node)
-	}
-	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
-func ValidateRelay(relay models.RelayRequest, update bool) error {
-	var err error
-
-	node, err := logic.GetNodeByID(relay.NodeID)
-	if err != nil {
-		return err
-	}
-	if !update && node.IsRelay {
-		return errors.New("node is already acting as a relay")
-	}
-	for _, relayedNodeID := range relay.RelayedNodes {
-		relayedNode, err := logic.GetNodeByID(relayedNodeID)
-		if err != nil {
-			return err
-		}
-		if relayedNode.IsIngressGateway {
-			return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")")
-		}
-		if relayedNode.IsInternetGateway {
-			return errors.New("cannot relay an internet gateway (" + relayedNodeID + ")")
-		}
-		if relayedNode.InternetGwID != "" && relayedNode.InternetGwID != relay.NodeID {
-			return errors.New("cannot relay an internet client (" + relayedNodeID + ")")
-		}
-		if relayedNode.IsFailOver {
-			return errors.New("cannot relay a failOver (" + relayedNodeID + ")")
-		}
-		if relayedNode.FailedOverBy != uuid.Nil {
-			ResetFailedOverPeer(&relayedNode)
-		}
-	}
-	return err
-}
-
-// UpdateRelayNodes - updates relay nodes
-func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []models.Node {
-	_ = SetRelayedNodes(false, relay, oldNodes)
-	return SetRelayedNodes(true, relay, newNodes)
-}
-
-func RelayUpdates(currentNode, newNode *models.Node) bool {
-	relayUpdates := false
-	if servercfg.IsPro && newNode.IsRelay {
-		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
-			relayUpdates = true
-		} else {
-			for i, node := range newNode.RelayedNodes {
-				if node != currentNode.RelayedNodes[i] {
-					relayUpdates = true
-				}
-			}
-		}
-	}
-	return relayUpdates
-}
-
-// UpdateRelayed - updates a relay's relayed nodes, and sends updates to the relayed nodes over MQ
-func UpdateRelayed(currentNode, newNode *models.Node) {
-	updatenodes := updateRelayNodes(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes)
-	if len(updatenodes) > 0 {
-		for _, relayedNode := range updatenodes {
-			node := relayedNode
-			ResetFailedOverPeer(&node)
-			go func() {
-				if err := mq.NodeUpdate(&node); err != nil {
-					slog.Error("error publishing node update to node", "node", node.ID, "error", err)
-				}
-
-			}()
-		}
-	}
-}
-
-// DeleteRelay - deletes a relay
-func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
-	var returnnodes []models.Node
-	node, err := logic.GetNodeByID(nodeid)
-	if err != nil {
-		return returnnodes, models.Node{}, err
-	}
-	returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes)
-	node.IsRelay = false
-	node.RelayedNodes = []string{}
-	node.SetLastModified()
-	if err = logic.UpsertNode(&node); err != nil {
-		return returnnodes, models.Node{}, err
-	}
-	return returnnodes, node, nil
-}
-
-func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet {
-	var allowedIPs = []net.IPNet{}
-	for _, relayedNodeID := range peer.RelayedNodes {
-		if node.ID.String() == relayedNodeID {
-			continue
-		}
-		relayedNode, err := logic.GetNodeByID(relayedNodeID)
-		if err != nil {
-			continue
-		}
-		allowed := getRelayedAddresses(relayedNodeID)
-		if relayedNode.IsEgressGateway {
-			allowed = append(allowed, logic.GetEgressIPs(&relayedNode)...)
-		}
-		allowedIPs = append(allowedIPs, allowed...)
-	}
-	return allowedIPs
-}
-
-// GetAllowedIpsForRelayed - returns the peerConfig for a node relayed by relay
-func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNet) {
-	if relayed.RelayedBy != relay.ID.String() {
-		logger.Log(0, "RelayedByRelay called with invalid parameters")
-		return
-	}
-	if relay.InternetGwID != "" {
-		return GetAllowedIpForInetNodeClient(relayed, relay)
-	}
-	peers, err := logic.GetNetworkNodes(relay.Network)
-	if err != nil {
-		logger.Log(0, "error getting network clients", err.Error())
-		return
-	}
-	for _, peer := range peers {
-		if peer.ID == relayed.ID || peer.ID == relay.ID {
-			continue
-		}
-		if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-			allowedIPs = append(allowedIPs, logic.GetAllowedIPs(relayed, &peer, nil)...)
-		}
-	}
-	return
-}
-
-func getRelayedAddresses(id string) []net.IPNet {
-	addrs := []net.IPNet{}
-	node, err := logic.GetNodeByID(id)
-	if err != nil {
-		logger.Log(0, "getRelayedAddresses: "+err.Error())
-		return addrs
-	}
-	if node.Address.IP != nil {
-		node.Address.Mask = net.CIDRMask(32, 32)
-		addrs = append(addrs, node.Address)
-	}
-	if node.Address6.IP != nil {
-		node.Address6.Mask = net.CIDRMask(128, 128)
-		addrs = append(addrs, node.Address6)
-	}
-	return addrs
-}

+ 23 - 0
pro/logic/status.go

@@ -17,6 +17,10 @@ func getNodeStatusOld(node *models.Node) {
 		node.Status = models.OnlineSt
 		node.Status = models.OnlineSt
 		return
 		return
 	}
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 		node.Status = models.OfflineSt
 		node.Status = models.OfflineSt
 		return
 		return
@@ -31,12 +35,25 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
 			node.Status = models.OfflineSt
 			node.Status = models.OfflineSt
 			return
 			return
 		}
 		}
+		ingNode, err := logic.GetNodeByID(node.StaticNode.IngressGatewayID)
+		if err != nil {
+			node.Status = models.OfflineSt
+			return
+		}
+		if !defaultEnabledPolicy {
+			allowed, _ := logic.IsNodeAllowedToCommunicate(*node, ingNode, false)
+			if !allowed {
+				node.Status = models.OnlineSt
+				return
+			}
+		}
 		// check extclient connection from metrics
 		// check extclient connection from metrics
 		ingressMetrics, err := GetMetrics(node.StaticNode.IngressGatewayID)
 		ingressMetrics, err := GetMetrics(node.StaticNode.IngressGatewayID)
 		if err != nil || ingressMetrics == nil || ingressMetrics.Connectivity == nil {
 		if err != nil || ingressMetrics == nil || ingressMetrics.Connectivity == nil {
 			node.Status = models.UnKnown
 			node.Status = models.UnKnown
 			return
 			return
 		}
 		}
+
 		if metric, ok := ingressMetrics.Connectivity[node.StaticNode.ClientID]; ok {
 		if metric, ok := ingressMetrics.Connectivity[node.StaticNode.ClientID]; ok {
 			if metric.Connected {
 			if metric.Connected {
 				node.Status = models.OnlineSt
 				node.Status = models.OnlineSt
@@ -46,9 +63,14 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
 				return
 				return
 			}
 			}
 		}
 		}
+
 		node.Status = models.UnKnown
 		node.Status = models.UnKnown
 		return
 		return
 	}
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > models.LastCheckInThreshold {
 	if time.Since(node.LastCheckIn) > models.LastCheckInThreshold {
 		node.Status = models.OfflineSt
 		node.Status = models.OfflineSt
 		return
 		return
@@ -197,6 +219,7 @@ func checkPeerConnectivity(node *models.Node, metrics *models.Metrics, defaultAc
 		peerNotConnectedCnt++
 		peerNotConnectedCnt++
 
 
 	}
 	}
+
 	if peerNotConnectedCnt > len(metrics.Connectivity)/2 {
 	if peerNotConnectedCnt > len(metrics.Connectivity)/2 {
 		node.Status = models.WarningSt
 		node.Status = models.WarningSt
 		return
 		return

+ 4 - 0
scripts/netmaker.default.env

@@ -96,3 +96,7 @@ MANAGE_DNS=false
 OLD_ACL_SUPPORT=true
 OLD_ACL_SUPPORT=true
 # if STUN is set to true, hole punch is called
 # if STUN is set to true, hole punch is called
 STUN=true
 STUN=true
+# Metrics Collection Port
+METRICS_PORT=51821
+# Metrics Collection interval in minutes
+PUBLISH_METRIC_INTERVAL=15

+ 2 - 2
scripts/nm-quick.sh

@@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$(realpath "$0")")
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
 NM_QUICK_VERSION="0.1.1"
 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:],\")
-
+BRANCH=master
 if [ $(id -u) -ne 0 ]; then
 if [ $(id -u) -ne 0 ]; then
 	echo "This script must be run as root"
 	echo "This script must be run as root"
 	exit 1
 	exit 1
@@ -617,7 +617,7 @@ install_netmaker() {
 
 
 	echo "Pulling config files..."
 	echo "Pulling config files..."
 
 
-	local BASE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master"
+	local BASE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BRANCH"
 	local COMPOSE_URL="$BASE_URL/compose/docker-compose.yml"
 	local COMPOSE_URL="$BASE_URL/compose/docker-compose.yml"
 	local CADDY_URL="$BASE_URL/docker/Caddyfile"
 	local CADDY_URL="$BASE_URL/docker/Caddyfile"
 	if [ "$INSTALL_TYPE" = "pro" ]; then
 	if [ "$INSTALL_TYPE" = "pro" ]; then

+ 17 - 0
servercfg/serverconf.go

@@ -14,6 +14,8 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 )
 )
 
 
+var ServerInfo = GetServerInfo()
+
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 const EmqxBrokerType = "emqx"
 const EmqxBrokerType = "emqx"
 
 
@@ -141,10 +143,12 @@ func GetServerInfo() models.ServerConfig {
 	cfg.Version = GetVersion()
 	cfg.Version = GetVersion()
 	cfg.IsPro = IsPro
 	cfg.IsPro = IsPro
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.MetricInterval = GetMetricInterval()
+	cfg.MetricsPort = GetMetricsPort()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.Stun = IsStunEnabled()
 	cfg.Stun = IsStunEnabled()
 	cfg.StunServers = GetStunServers()
 	cfg.StunServers = GetStunServers()
 	cfg.DefaultDomain = GetDefaultDomain()
 	cfg.DefaultDomain = GetDefaultDomain()
+	cfg.EndpointDetection = IsEndpointDetectionEnabled()
 	return cfg
 	return cfg
 }
 }
 
 
@@ -654,6 +658,19 @@ func GetMqUserName() string {
 	return password
 	return password
 }
 }
 
 
+// GetMetricsPort - get metrics port
+func GetMetricsPort() int {
+	p := 51821
+	if os.Getenv("METRICS_PORT") != "" {
+		pStr := os.Getenv("METRICS_PORT")
+		pInt, err := strconv.Atoi(pStr)
+		if err == nil && pInt != 0 {
+			p = pInt
+		}
+	}
+	return p
+}
+
 // GetMetricInterval - get the publish metric interval
 // GetMetricInterval - get the publish metric interval
 func GetMetricIntervalInMinutes() time.Duration {
 func GetMetricIntervalInMinutes() time.Duration {
 	//default 15 minutes
 	//default 15 minutes

+ 2 - 26
serverctl/serverctl.go

@@ -59,32 +59,8 @@ func setNetworkDefaults() error {
 		return err
 		return err
 	}
 	}
 	for _, network := range networks {
 	for _, network := range networks {
-		update := false
-		newNet := network
-		if strings.Contains(network.NetID, ".") {
-			newNet.NetID = strings.ReplaceAll(network.NetID, ".", "")
-			newNet.DefaultInterface = strings.ReplaceAll(network.DefaultInterface, ".", "")
-			update = true
-		}
-		if strings.ContainsAny(network.NetID, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
-			newNet.NetID = strings.ToLower(network.NetID)
-			newNet.DefaultInterface = strings.ToLower(network.DefaultInterface)
-			update = true
-		}
-		if update {
-			newNet.SetDefaults()
-			if err := logic.SaveNetwork(&newNet); err != nil {
-				logger.Log(0, "error saving networks during initial update:", err.Error())
-			}
-			if err := logic.DeleteNetwork(network.NetID); err != nil {
-				logger.Log(0, "error deleting old network:", err.Error())
-			}
-		} else {
-			network.SetDefaults()
-			_, _, _, err = logic.UpdateNetwork(&network, &network)
-			if err != nil {
-				logger.Log(0, "could not set defaults on network", network.NetID)
-			}
+		if network.SetDefaults() {
+			logic.SaveNetwork(&network)
 		}
 		}
 	}
 	}
 	return nil
 	return nil

+ 21 - 1
utils/utils.go

@@ -1,6 +1,10 @@
 package utils
 package utils
 
 
-import "time"
+import (
+	"log/slog"
+	"runtime"
+	"time"
+)
 
 
 // RetryStrategy specifies a strategy to retry an operation after waiting a while,
 // RetryStrategy specifies a strategy to retry an operation after waiting a while,
 // with hooks for successful and unsuccessful (>=max) tries.
 // with hooks for successful and unsuccessful (>=max) tries.
@@ -39,3 +43,19 @@ func (rs RetryStrategy) DoStrategy() {
 		return
 		return
 	}
 	}
 }
 }
+
+func TraceCaller() {
+	// Skip 1 frame to get the caller of this function
+	pc, file, line, ok := runtime.Caller(2)
+	if !ok {
+		slog.Debug("Unable to get caller information")
+		return
+	}
+
+	// Get function name from the program counter (pc)
+	funcName := runtime.FuncForPC(pc).Name()
+
+	// Print trace details
+	slog.Debug("Called from function: %s\n", "func-name", funcName)
+	slog.Debug("File: %s, Line: %d\n", "file", file, "line-no", line)
+}