Przeglądaj źródła

resolve merge conflicts

abhishek9686 6 miesięcy temu
rodzic
commit
225bf37e02

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

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

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

@@ -12,7 +12,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     steps:
       - name: get logs
-        uses: dawidd6/action-download-artifact@v7
+        uses: dawidd6/action-download-artifact@v8
         with:
           run_id: ${{ github.event.workflow_run.id}}
           if_no_artifact_found: warn
@@ -75,7 +75,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     steps:
       - name: get logs
-        uses: dawidd6/action-download-artifact@v7
+        uses: dawidd6/action-download-artifact@v8
         with:
           run_id: ${{ github.event.workflow_run.id}}
           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 go build -tags=ee . -o netmaker main.go
-FROM alpine:3.21.0
+FROM alpine:3.21.2
 
 # add a c lib
 # set the working directory
 WORKDIR /root/
+RUN apk update && apk upgrade
+RUN apk add --no-cache sqlite
 RUN mkdir -p /etc/netclient/config
 COPY --from=builder /app/netmaker .
 COPY --from=builder /app/config config

+ 1 - 1
Dockerfile-quick

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

+ 6 - 6
README.md

@@ -49,14 +49,14 @@
 
 # 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  
 
-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.
 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?
 
-- 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. 
 
-- 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.
 

+ 2 - 5
compose/docker-compose.yml

@@ -52,11 +52,8 @@ services:
       - caddy_data:/data
       - caddy_conf:/config
     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:
     #network_mode: host

+ 8 - 7
config/config.go

@@ -37,7 +37,7 @@ type ServerConfig struct {
 	APIConnString              string        `yaml:"apiconn"`
 	APIHost                    string        `yaml:"apihost"`
 	APIPort                    string        `yaml:"apiport"`
-	Broker                     string        `yam:"broker"`
+	Broker                     string        `yaml:"broker"`
 	ServerBrokerEndpoint       string        `yaml:"serverbrokerendpoint"`
 	BrokerType                 string        `yaml:"brokertype"`
 	EmqxRestEndpoint           string        `yaml:"emqxrestendpoint"`
@@ -92,14 +92,15 @@ type ServerConfig struct {
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
-	EndpointDetection          bool          `json:"endpoint_detection"`
+	EndpointDetection          bool          `yaml:"endpoint_detection"`
 	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"`
+	MetricsPort                int           `yaml:"metrics_port"`
 	ManageDNS                  bool          `yaml:"manage_dns"`
 	Stun                       bool          `yaml:"stun"`
 	StunServers                string        `yaml:"stun_servers"`

+ 1 - 0
controllers/controller.go

@@ -24,6 +24,7 @@ var HttpMiddlewares = []mux.MiddlewareFunc{
 // HttpHandlers - handler functions for REST interactions
 var HttpHandlers = []interface{}{
 	nodeHandlers,
+	gwHandlers,
 	userHandlers,
 	networkHandlers,
 	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"
 	"fmt"
 	"net/http"
-	"os"
+	"time"
 
 	"github.com/google/uuid"
 	"github.com/gorilla/mux"
@@ -24,6 +24,10 @@ func hostHandlers(r *mux.Router) {
 		Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).
 		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))).
 		Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))).
@@ -45,16 +49,64 @@ func hostHandlers(r *mux.Router) {
 		Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))).
 		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))).
 		Methods(http.MethodDelete)
 	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
 // @Router      /api/hosts/{hostid}/upgrade [put]
 // @Tags        Hosts
 // @Security    oauth
 // @Param       hostid path string true "Host ID"
+// @Param       force query bool false "Force upgrade"
 // @Success     200 {string} string "passed message to upgrade host"
 // @Failure     500 {object} models.ErrorResponse
 // 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"))
 		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)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
@@ -176,17 +235,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
 			slog.Error("failed to get node:", "id", node.ID, "error", err)
 			continue
 		}
-		if node.FailedOverBy != uuid.Nil {
+		if node.FailedOverBy != uuid.Nil && r.URL.Query().Get("reset_failovered") == "true" {
 			logic.ResetFailedOverPeer(&node)
 			sendPeerUpdate = true
 		}
 	}
 	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())
 		}
 	}
@@ -320,7 +375,7 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		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 {
 	case models.CheckIn:
 		sendPeerUpdate = mq.HandleHostCheckin(&hostUpdate.Host, currentHost)
@@ -537,7 +592,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 					w,
 					r,
 					logic.FormatError(
-						fmt.Errorf("failed to force delete daemon node: "+err.Error()),
+						fmt.Errorf("failed to force delete daemon node: %s", err.Error()),
 						"internal",
 					),
 				)
@@ -577,7 +632,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 					w,
 					r,
 					logic.FormatError(
-						fmt.Errorf("failed to force delete daemon node: "+err.Error()),
+						fmt.Errorf("failed to force delete daemon node: %s", err.Error()),
 						"internal",
 					),
 				)
@@ -860,6 +915,45 @@ func updateKeys(w http.ResponseWriter, r *http.Request) {
 	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
 // @Router      /api/hosts/{hostid}/sync [post]
 // @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)
 }
 
@@ -927,3 +1021,33 @@ func delEmqxHosts(w http.ResponseWriter, r *http.Request) {
 	}
 	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
 // @Security    oauth
 // @Param       networkname path string true "Network name"
+// @Param       force query bool false "Force Delete"
 // @Produce     json
 // @Success     200 {object} models.SuccessResponse
 // @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) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
-
+	force := r.URL.Query().Get("force") == "true"
 	var params = mux.Vars(r)
 	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 {
 		errtype := "badrequest"
 		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))
 	//delete network from allocated ip map
 	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)
 	w.WriteHeader(http.StatusOK)
 	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("DeleteExistingNetwork", func(t *testing.T) {
-		err := logic.DeleteNetwork("skynet")
+		doneCh := make(chan struct{}, 1)
+		err := logic.DeleteNetwork("skynet", false, doneCh)
 		assert.Nil(t, err)
 	})
 	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)
 	})
 }
@@ -214,6 +222,15 @@ func createNet() {
 		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() {
 	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}/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}/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/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
 // @Router      /api/nodes/{network}/{nodeid} [put]
 // @Tags        Nodes

+ 3 - 0
database/database.go

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

+ 5 - 5
go.mod

@@ -5,7 +5,7 @@ go 1.23
 require (
 	github.com/blang/semver v3.5.1+incompatible
 	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/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
@@ -18,10 +18,10 @@ require (
 	github.com/stretchr/testify v1.10.0
 	github.com/txn2/txeh v1.5.5
 	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/sys v0.28.0 // indirect
+	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	gopkg.in/yaml.v3 v3.0.1
@@ -50,7 +50,7 @@ require (
 
 require (
 	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/inconshreveable/mousetrap v1.1.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/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 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/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 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/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/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/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 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-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.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/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 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.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.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/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 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.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.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 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()
 	defer aclCacheMutex.Unlock()
 	aclCacheMap[a.ID] = a
+
 }
 
 func removeAclFromCache(a models.Acl) {
@@ -599,6 +600,7 @@ func IsPeerAllowed(node, peer models.Node, checkDefaultPolicy bool) bool {
 				return true
 			}
 		}
+
 	}
 	// list device policies
 	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)
 	nodes, _ := GetNetworkNodes(node.Network)
 	nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...)
-	//fmt.Printf("=====> NODES: %+v \n\n", nodes)
 	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network))
-	//fmt.Printf("=====> USER NODES %+v \n\n", userNodes)
 	for _, userNodeI := range userNodes {
 		for _, peer := range nodes {
 			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
 	}
 	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())
 	if err != nil {
 		return models.Node{}, err
 	}
 	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)
@@ -156,12 +156,16 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 		return models.Node{}, err
 	}
 	node.IsIngressGateway = true
+	node.IsGw = true
 	if !servercfg.IsPro {
 		node.IsInternetGateway = ingress.IsInternetGateway
 	}
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange6 = network.AddressRange6
 	node.IngressDNS = ingress.ExtclientDNS
+	if node.IsInternetGateway && node.IngressDNS == "" {
+		node.IngressDNS = "1.1.1.1"
+	}
 	node.IngressPersistentKeepalive = 20
 	if ingress.PersistentKeepalive != 0 {
 		node.IngressPersistentKeepalive = ingress.PersistentKeepalive

+ 46 - 18
logic/networks.go

@@ -118,7 +118,7 @@ func AddNetworkToAllocatedIpMap(networkName string) {
 		return
 	}
 	networkCacheMutex.Lock()
-	allocatedIpMap[networkName] = map[string]net.IP{}
+	allocatedIpMap[networkName] = make(map[string]net.IP)
 	networkCacheMutex.Unlock()
 }
 
@@ -190,23 +190,8 @@ func GetNetworks() ([]models.Network, error) {
 }
 
 // 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)
 	if nodeCount == 0 || database.IsEmptyRecord(err) {
 		// delete server nodes first then db records
@@ -219,7 +204,50 @@ func DeleteNetwork(network string) error {
 		}
 		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

+ 2 - 2
logic/nodes.go

@@ -147,7 +147,7 @@ func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node
 		defer nodeNetworkCacheMutex.Unlock()
 		return slices.Collect(maps.Values(networkNodes))
 	}
-	var nodes = []models.Node{}
+	var nodes = make([]models.Node, 0, len(allNodes))
 	for i := range allNodes {
 		node := allNodes[i]
 		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

+ 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
 func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.Node,
 	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),
 			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() {
 		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)...)
 			}
 			_, 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
 				}
-				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
@@ -297,15 +369,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				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 &&
 				!peer.PendingDelete &&
 				peer.Connected &&
 				nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
 				(defaultDevicePolicy.Enabled || allowedToComm) &&
 				(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
@@ -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
 }
 

+ 225 - 13
logic/relay.go

@@ -1,34 +1,246 @@
 package logic
 
 import (
+	"errors"
+	"fmt"
 	"net"
 
+	"github.com/google/uuid"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"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
 		return
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 		node.Status = models.OfflineSt
 		return

+ 1 - 1
logic/tags.go

@@ -259,7 +259,7 @@ func CheckIDSyntax(id string) error {
 	if len(id) < 3 {
 		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 {
 		return err
 	}

+ 25 - 0
logic/util.go

@@ -7,6 +7,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"log/slog"
 	"net"
 	"os"
 	"strings"
@@ -91,6 +92,30 @@ func StringSliceContains(slice []string, item string) bool {
 	}
 	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
 func NormalizeCIDR(address string) (string, error) {

+ 16 - 0
migrate/migrate.go

@@ -28,6 +28,7 @@ func Run() {
 	updateHosts()
 	updateNodes()
 	updateAcls()
+	migrateToGws()
 }
 
 func assignSuperAdmin() {
@@ -441,3 +442,18 @@ func createDefaultTagsAndPolicies() {
 	}
 	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 (
 	// Upgrade - const to request host to update it's client
 	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 HostMqAction = "SIGNAL_HOST"
 	// 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"`
 	IsStaticPort bool    `json:"is_static_port"`
 	IsStatic     bool    `json:"is_static"`
+	Version      string  `json:"version"`
 }
 
 // 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"
 )
 
+type HostPeerInfo struct {
+	NetworkPeerIDs map[NetworkID]PeerMap `json:"network_peers"`
+}
+
 // HostPeerUpdate - struct for host peer updates
 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"`
-	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 {

+ 12 - 1
models/network.go

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

+ 11 - 9
models/node.go

@@ -15,11 +15,12 @@ import (
 type NodeStatus string
 
 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
@@ -78,11 +79,12 @@ type CommonNode struct {
 	Action              string    `json:"action"              yaml:"action"`
 	LocalAddress        net.IPNet `json:"localaddress"        yaml:"localaddress"`
 	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"`
-	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"`
 	IngressDNS          string    `json:"ingressdns"          yaml:"ingressdns"`
 	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
 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

+ 1 - 0
mq/handlers.go

@@ -280,6 +280,7 @@ func HandleHostCheckin(h, currentHost *models.Host) bool {
 		(h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) ||
 		(h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort) || (!h.EndpointIPv6.Equal(currentHost.EndpointIPv6))
 	if ifaceDelta { // only save if something changes
+
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIPv6 = h.EndpointIPv6
 		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
 func PublishPeerUpdate(replacePeers bool) error {
-
 	if !servercfg.IsMessageQueueBackend() {
 		return nil
 	}
@@ -114,6 +113,11 @@ func PublishSingleHostPeerUpdate(host *models.Host, allNodes []models.Node, dele
 	if err != nil {
 		return err
 	}
+	peerUpdate.OldPeerUpdateFields = models.OldPeerUpdateFields{
+		NodePeers:         peerUpdate.NodePeers,
+		OldPeers:          peerUpdate.Peers,
+		EndpointDetection: peerUpdate.ServerConfig.EndpointDetection,
+	}
 	peerUpdate.ReplacePeers = replacePeers
 	data, err := json.Marshal(&peerUpdate)
 	if err != nil {

+ 137 - 4
pro/controllers/failover.go

@@ -19,7 +19,7 @@ import (
 
 // FailOverHandlers - handlers for FailOver
 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)
 	r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))).
 		Methods(http.MethodPost)
@@ -29,6 +29,8 @@ func FailOverHandlers(r *mux.Router) {
 		Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).
 		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
@@ -44,7 +46,6 @@ func getfailOver(w http.ResponseWriter, r *http.Request) {
 	// confirm host exists
 	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
-		slog.Error("failed to get node:", "node", nodeid, "error", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
@@ -140,6 +141,7 @@ func deletefailOver(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	proLogic.RemoveFailOverFromCache(node.Network)
 	go func() {
 		proLogic.ResetFailOver(&node)
 		mq.PublishPeerUpdate(false)
@@ -265,10 +267,9 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
-
 	err = proLogic.SetFailOverCtx(failOverNode, node, peerNode)
 	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)
 		logic.ReturnErrorResponse(
 			w,
@@ -293,3 +294,135 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	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)
 	if err != nil {
 		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,
 		proControllers.MetricHandlers,
-		proControllers.RelayHandlers,
 		proControllers.UserHandlers,
 		proControllers.FailOverHandlers,
 		proControllers.InetHandlers,
@@ -91,6 +90,7 @@ func InitPro() {
 			slog.Error("no OAuth provider found or not configured, continuing without OAuth")
 		}
 		proLogic.LoadNodeMetricsToCache()
+		proLogic.InitFailOverCache()
 	})
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
@@ -106,13 +106,6 @@ func InitPro() {
 	logic.GetMetrics = proLogic.GetMetrics
 	logic.UpdateMetrics = proLogic.UpdateMetrics
 	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.SetDefaultGw = proLogic.SetDefaultGw
 	logic.SetDefaultGwForRelayedUpdate = proLogic.SetDefaultGwForRelayedUpdate

+ 67 - 12
pro/logic/failover.go

@@ -13,7 +13,49 @@ import (
 )
 
 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 {
 	failOverCtxMutex.Lock()
 	defer failOverCtxMutex.Unlock()
@@ -23,13 +65,16 @@ func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
 	if victimNode.FailOverPeers == nil {
 		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{}{}
 	victimNode.FailOverPeers[peerNode.ID.String()] = struct{}{}
 	victimNode.FailedOverBy = failOverNode.ID
 	peerNode.FailedOverBy = failOverNode.ID
-	if err := logic.UpsertNode(&failOverNode); err != nil {
-		return err
-	}
 	if err := logic.UpsertNode(&victimNode); err != nil {
 		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")
 }
 
+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
 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
@@ -185,5 +239,6 @@ func CreateFailOver(node models.Node) error {
 		slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
 		return err
 	}
+	SetFailOverInCache(node)
 	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
 		return
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > time.Minute*10 {
 		node.Status = models.OfflineSt
 		return
@@ -31,12 +35,25 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
 			node.Status = models.OfflineSt
 			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
 		ingressMetrics, err := GetMetrics(node.StaticNode.IngressGatewayID)
 		if err != nil || ingressMetrics == nil || ingressMetrics.Connectivity == nil {
 			node.Status = models.UnKnown
 			return
 		}
+
 		if metric, ok := ingressMetrics.Connectivity[node.StaticNode.ClientID]; ok {
 			if metric.Connected {
 				node.Status = models.OnlineSt
@@ -46,9 +63,14 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
 				return
 			}
 		}
+
 		node.Status = models.UnKnown
 		return
 	}
+	if !node.Connected {
+		node.Status = models.Disconnected
+		return
+	}
 	if time.Since(node.LastCheckIn) > models.LastCheckInThreshold {
 		node.Status = models.OfflineSt
 		return
@@ -197,6 +219,7 @@ func checkPeerConnectivity(node *models.Node, metrics *models.Metrics, defaultAc
 		peerNotConnectedCnt++
 
 	}
+
 	if peerNotConnectedCnt > len(metrics.Connectivity)/2 {
 		node.Status = models.WarningSt
 		return

+ 4 - 0
scripts/netmaker.default.env

@@ -96,3 +96,7 @@ MANAGE_DNS=false
 OLD_ACL_SUPPORT=true
 # if STUN is set to true, hole punch is called
 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"
 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:],\")
-
+BRANCH=master
 if [ $(id -u) -ne 0 ]; then
 	echo "This script must be run as root"
 	exit 1
@@ -617,7 +617,7 @@ install_netmaker() {
 
 	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 CADDY_URL="$BASE_URL/docker/Caddyfile"
 	if [ "$INSTALL_TYPE" = "pro" ]; then

+ 17 - 0
servercfg/serverconf.go

@@ -14,6 +14,8 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
+var ServerInfo = GetServerInfo()
+
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 const EmqxBrokerType = "emqx"
 
@@ -141,10 +143,12 @@ func GetServerInfo() models.ServerConfig {
 	cfg.Version = GetVersion()
 	cfg.IsPro = IsPro
 	cfg.MetricInterval = GetMetricInterval()
+	cfg.MetricsPort = GetMetricsPort()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.Stun = IsStunEnabled()
 	cfg.StunServers = GetStunServers()
 	cfg.DefaultDomain = GetDefaultDomain()
+	cfg.EndpointDetection = IsEndpointDetectionEnabled()
 	return cfg
 }
 
@@ -654,6 +658,19 @@ func GetMqUserName() string {
 	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
 func GetMetricIntervalInMinutes() time.Duration {
 	//default 15 minutes

+ 2 - 26
serverctl/serverctl.go

@@ -59,32 +59,8 @@ func setNetworkDefaults() error {
 		return err
 	}
 	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

+ 21 - 1
utils/utils.go

@@ -1,6 +1,10 @@
 package utils
 
-import "time"
+import (
+	"log/slog"
+	"runtime"
+	"time"
+)
 
 // RetryStrategy specifies a strategy to retry an operation after waiting a while,
 // with hooks for successful and unsuccessful (>=max) tries.
@@ -39,3 +43,19 @@ func (rs RetryStrategy) DoStrategy() {
 		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)
+}