Procházet zdrojové kódy

merge conflicts resolved

Abhishek Kondur před 2 roky
rodič
revize
b29f2f09c9

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

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

+ 1 - 1
.github/workflows/branchtest.yml

@@ -21,7 +21,7 @@ jobs:
     needs: skip-check
     if: ${{ needs.skip-check.outputs.skip != 'true' }}
     outputs:
-      netclientbranch: ${{ steps.checkbranch.outputs.netclientbranch }}
+      netclientbranch: ${{ steps.getbranch.outputs.netclientbranch }}
     steps:
       - name: checkout
         uses: actions/checkout@v3

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ 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.18.0
+FROM alpine:3.18.2
 
 # add a c lib
 # set the working directory

+ 1 - 1
Dockerfile-quick

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

+ 3 - 4
README.md

@@ -6,18 +6,17 @@
 </p>
 
 <p align="center">
-<a href="https://runacap.com/ross-index/q1-2022/" target="_blank" rel="noopener">
-    <img src="https://runacap.com/wp-content/uploads/2022/06/ROSS_badge_white_Q1_2022.svg" alt="ROSS Index - Fastest Growing Open-Source Startups in Q1 2022 | Runa Capital"  width="15%"/>
+<a href="https://runacap.com/ross-index/annual-2022/" target="_blank" rel="noopener">
+    <img src="https://runacap.com/wp-content/uploads/2023/02/Annual_ROSS_badge_white_2022.svg" alt="ROSS Index - Fastest Growing Open-Source Startups | Runa Capital" width="17%" />
 </a>  
 <a href="https://www.ycombinator.com/companies/netmaker/" target="_blank" rel="noopener">
     <img src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/y-combinator.png" alt="Y-Combinator" width="16%" />
 </a>  
-
 </p>
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.20.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.20.3-informational?style=flat-square" />
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 0 - 22
cli/cmd/host/create_relay.go

@@ -1,22 +0,0 @@
-package host
-
-import (
-	"strings"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var hostCreateRelayCmd = &cobra.Command{
-	Use:   "create_relay [HOST ID] [RELAYED HOST IDS (comma separated)]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Turn a Host into a Relay",
-	Long:  `Turn a Host into a Relay`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.CreateRelay(args[0], strings.Split(args[1], ",")))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(hostCreateRelayCmd)
-}

+ 22 - 0
cli/cmd/node/create_relay.go

@@ -0,0 +1,22 @@
+package node
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var hostCreateRelayCmd = &cobra.Command{
+	Use:   "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Turn a Node into a Relay",
+	Long:  `Turn a Node into a Relay`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ",")))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(hostCreateRelayCmd)
+}

+ 6 - 6
cli/cmd/host/delete_relay.go → cli/cmd/node/delete_relay.go

@@ -1,4 +1,4 @@
-package host
+package node
 
 import (
 	"github.com/gravitl/netmaker/cli/functions"
@@ -6,12 +6,12 @@ import (
 )
 
 var hostDeleteRelayCmd = &cobra.Command{
-	Use:   "delete_relay [HOST ID]",
-	Args:  cobra.ExactArgs(1),
-	Short: "Delete Relay role from a host",
-	Long:  `Delete Relay role from a host`,
+	Use:   "delete_relay [NETWORK] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete Relay from a node",
+	Long:  `Delete Relay from a node`,
 	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.DeleteRelay(args[0]))
+		functions.PrettyPrint(functions.DeleteRelay(args[0], args[1]))
 	},
 }
 

+ 1 - 1
cli/cmd/node/flags.go

@@ -12,7 +12,7 @@ var (
 	postUp                 string
 	postDown               string
 	keepAlive              int
-	relayAddrs             string
+	relayedNodes           string
 	egressGatewayRanges    string
 	expirationDateTime     int
 	defaultACL             bool

+ 3 - 3
cli/cmd/node/update.go

@@ -35,8 +35,8 @@ var nodeUpdateCmd = &cobra.Command{
 			node.Address6 = address6
 			node.LocalAddress = localAddress
 			node.PersistentKeepalive = int32(keepAlive)
-			if relayAddrs != "" {
-				node.RelayAddrs = strings.Split(relayAddrs, ",")
+			if relayedNodes != "" {
+				node.RelayedNodes = strings.Split(relayedNodes, ",")
 			}
 			if egressGatewayRanges != "" {
 				node.EgressGatewayRanges = strings.Split(egressGatewayRanges, ",")
@@ -62,7 +62,7 @@ func init() {
 	nodeUpdateCmd.Flags().StringVar(&postUp, "post_up", "", "Commands to run after node is up `;` separated")
 	nodeUpdateCmd.Flags().StringVar(&postDown, "post_down", "", "Commands to run after node is down `;` separated")
 	nodeUpdateCmd.Flags().IntVar(&keepAlive, "keep_alive", 0, "Interval in which packets are sent to keep connections open with peers")
-	nodeUpdateCmd.Flags().StringVar(&relayAddrs, "relay_addrs", "", "Addresses for relaying connections if node acts as a relay")
+	nodeUpdateCmd.Flags().StringVar(&relayedNodes, "relayed_nodes", "", "relayed nodes if node acts as a relay")
 	nodeUpdateCmd.Flags().StringVar(&egressGatewayRanges, "egress_addrs", "", "Addresses for egressing traffic if node acts as an egress")
 	nodeUpdateCmd.Flags().IntVar(&expirationDateTime, "expiry", 0, "UNIX timestamp after which node will lose access to the network")
 	nodeUpdateCmd.Flags().BoolVar(&defaultACL, "acl", false, "Enable default ACL ?")

+ 9 - 8
cli/functions/host.go

@@ -36,17 +36,18 @@ func DeleteHostFromNetwork(hostID, network string) *hostNetworksUpdatePayload {
 	return request[hostNetworksUpdatePayload](http.MethodDelete, "/api/hosts/"+hostID+"/networks/"+network, nil)
 }
 
-// CreateRelay - turn a host into a relay
-func CreateRelay(hostID string, relayedHosts []string) *models.ApiHost {
-	return request[models.ApiHost](http.MethodPost, fmt.Sprintf("/api/hosts/%s/relay", hostID), &models.HostRelayRequest{
-		HostID:       hostID,
-		RelayedHosts: relayedHosts,
+// CreateRelay - add relay to a node
+func CreateRelay(netID, nodeID string, relayedNodes []string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createrelay", netID, nodeID), &models.RelayRequest{
+		NodeID:       nodeID,
+		NetID:        netID,
+		RelayedNodes: relayedNodes,
 	})
 }
 
-// DeleteRelay - remove relay role from a host
-func DeleteRelay(hostID string) *models.ApiHost {
-	return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s/relay", hostID), nil)
+// DeleteRelay - remove relay from a node
+func DeleteRelay(netID, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleterelay", netID, nodeID), nil)
 }
 
 // RefreshKeys - refresh wireguard keys

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

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

+ 2 - 1
compose/docker-compose.yml

@@ -6,7 +6,7 @@ services:
     container_name: netmaker
     image: gravitl/netmaker:$SERVER_IMAGE_TAG
     env_file: ./netmaker.env
-    restart: on-failure
+    restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
@@ -96,6 +96,7 @@ services:
     network_mode: "host"
     volumes:
       - turn_server:/etc/config
+    restart: always
 
 volumes:
   caddy_data: { } # runtime data for caddy

+ 56 - 55
config/config.go

@@ -32,61 +32,62 @@ type EnvironmentConfig struct {
 
 // ServerConfig - server conf struct
 type ServerConfig struct {
-	CoreDNSAddr          string    `yaml:"corednsaddr"`
-	APIConnString        string    `yaml:"apiconn"`
-	APIHost              string    `yaml:"apihost"`
-	APIPort              string    `yaml:"apiport"`
-	Broker               string    `yam:"broker"`
-	ServerBrokerEndpoint string    `yaml:"serverbrokerendpoint"`
-	BrokerType           string    `yaml:"brokertype"`
-	EmqxRestEndpoint     string    `yaml:"emqxrestendpoint"`
-	NetclientAutoUpdate  string    `yaml:"netclientautoupdate"`
-	MasterKey            string    `yaml:"masterkey"`
-	DNSKey               string    `yaml:"dnskey"`
-	AllowedOrigin        string    `yaml:"allowedorigin"`
-	NodeID               string    `yaml:"nodeid"`
-	RestBackend          string    `yaml:"restbackend"`
-	MessageQueueBackend  string    `yaml:"messagequeuebackend"`
-	DNSMode              string    `yaml:"dnsmode"`
-	DisableRemoteIPCheck string    `yaml:"disableremoteipcheck"`
-	Version              string    `yaml:"version"`
-	SQLConn              string    `yaml:"sqlconn"`
-	Platform             string    `yaml:"platform"`
-	Database             string    `yaml:"database"`
-	Verbosity            int32     `yaml:"verbosity"`
-	AuthProvider         string    `yaml:"authprovider"`
-	OIDCIssuer           string    `yaml:"oidcissuer"`
-	ClientID             string    `yaml:"clientid"`
-	ClientSecret         string    `yaml:"clientsecret"`
-	FrontendURL          string    `yaml:"frontendurl"`
-	DisplayKeys          string    `yaml:"displaykeys"`
-	AzureTenant          string    `yaml:"azuretenant"`
-	Telemetry            string    `yaml:"telemetry"`
-	HostNetwork          string    `yaml:"hostnetwork"`
-	Server               string    `yaml:"server"`
-	PublicIPService      string    `yaml:"publicipservice"`
-	MQPassword           string    `yaml:"mqpassword"`
-	MQUserName           string    `yaml:"mqusername"`
-	MetricsExporter      string    `yaml:"metrics_exporter"`
-	BasicAuth            string    `yaml:"basic_auth"`
-	LicenseValue         string    `yaml:"license_value"`
-	NetmakerAccountID    string    `yaml:"netmaker_account_id"`
-	IsEE                 string    `yaml:"is_ee"`
-	StunPort             int       `yaml:"stun_port"`
-	StunList             string    `yaml:"stun_list"`
-	Proxy                string    `yaml:"proxy"`
-	DefaultProxyMode     ProxyMode `yaml:"defaultproxymode"`
-	UsersLimit           int       `yaml:"user_limit"`
-	ClientsLimit         int       `yaml:"client_limit"`
-	NetworksLimit        int       `yaml:"network_limit"`
-	HostsLimit           int       `yaml:"host_limit"`
-	DeployedByOperator   bool      `yaml:"deployed_by_operator"`
-	TurnServer           string    `yaml:"turn_server"`
-	TurnApiServer        string    `yaml:"turn_api_server"`
-	TurnPort             int       `yaml:"turn_port"`
-	TurnUserName         string    `yaml:"turn_username"`
-	TurnPassword         string    `yaml:"turn_password"`
-	UseTurn              bool      `yaml:"use_turn"`
+	CoreDNSAddr                string    `yaml:"corednsaddr"`
+	APIConnString              string    `yaml:"apiconn"`
+	APIHost                    string    `yaml:"apihost"`
+	APIPort                    string    `yaml:"apiport"`
+	Broker                     string    `yam:"broker"`
+	ServerBrokerEndpoint       string    `yaml:"serverbrokerendpoint"`
+	BrokerType                 string    `yaml:"brokertype"`
+	EmqxRestEndpoint           string    `yaml:"emqxrestendpoint"`
+	NetclientAutoUpdate        string    `yaml:"netclientautoupdate"`
+	NetclientEndpointDetection string    `yaml:"netclientendpointdetection"`
+	MasterKey                  string    `yaml:"masterkey"`
+	DNSKey                     string    `yaml:"dnskey"`
+	AllowedOrigin              string    `yaml:"allowedorigin"`
+	NodeID                     string    `yaml:"nodeid"`
+	RestBackend                string    `yaml:"restbackend"`
+	MessageQueueBackend        string    `yaml:"messagequeuebackend"`
+	DNSMode                    string    `yaml:"dnsmode"`
+	DisableRemoteIPCheck       string    `yaml:"disableremoteipcheck"`
+	Version                    string    `yaml:"version"`
+	SQLConn                    string    `yaml:"sqlconn"`
+	Platform                   string    `yaml:"platform"`
+	Database                   string    `yaml:"database"`
+	Verbosity                  int32     `yaml:"verbosity"`
+	AuthProvider               string    `yaml:"authprovider"`
+	OIDCIssuer                 string    `yaml:"oidcissuer"`
+	ClientID                   string    `yaml:"clientid"`
+	ClientSecret               string    `yaml:"clientsecret"`
+	FrontendURL                string    `yaml:"frontendurl"`
+	DisplayKeys                string    `yaml:"displaykeys"`
+	AzureTenant                string    `yaml:"azuretenant"`
+	Telemetry                  string    `yaml:"telemetry"`
+	HostNetwork                string    `yaml:"hostnetwork"`
+	Server                     string    `yaml:"server"`
+	PublicIPService            string    `yaml:"publicipservice"`
+	MQPassword                 string    `yaml:"mqpassword"`
+	MQUserName                 string    `yaml:"mqusername"`
+	MetricsExporter            string    `yaml:"metrics_exporter"`
+	BasicAuth                  string    `yaml:"basic_auth"`
+	LicenseValue               string    `yaml:"license_value"`
+	NetmakerAccountID          string    `yaml:"netmaker_account_id"`
+	IsEE                       string    `yaml:"is_ee"`
+	StunPort                   int       `yaml:"stun_port"`
+	StunList                   string    `yaml:"stun_list"`
+	Proxy                      string    `yaml:"proxy"`
+	DefaultProxyMode           ProxyMode `yaml:"defaultproxymode"`
+	TurnServer                 string    `yaml:"turn_server"`
+	TurnApiServer              string    `yaml:"turn_api_server"`
+	TurnPort                   int       `yaml:"turn_port"`
+	TurnUserName               string    `yaml:"turn_username"`
+	TurnPassword               string    `yaml:"turn_password"`
+	UseTurn                    bool      `yaml:"use_turn"`
+	UsersLimit                 int       `yaml:"user_limit"`
+	ClientsLimit               int       `yaml:"client_limit"`
+	NetworksLimit              int       `yaml:"network_limit"`
+	HostsLimit                 int       `yaml:"host_limit"`
+	DeployedByOperator         bool      `yaml:"deployed_by_operator"`
 }
 
 // ProxyMode - default proxy mode for server

+ 1 - 1
controllers/docs.go

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

+ 4 - 42
controllers/hosts.go

@@ -6,7 +6,6 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
-	"reflect"
 
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
@@ -26,11 +25,9 @@ func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(deleteHostFromNetwork))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(createHostRelay))).Methods(http.MethodPost)
-	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost)
-	r.HandleFunc("/api/v1/host", authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
-	r.HandleFunc("/api/v1/host/{hostid}/signalpeer", authorize(true, false, "host", http.HandlerFunc(signalPeer))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/host", Authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/host/{hostid}/signalpeer", Authorize(true, false, "host", http.HandlerFunc(signalPeer))).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 }
 
@@ -130,9 +127,11 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+
 	serverConf.TrafficKey = key
 	response := models.HostPull{
 		Host:         *host,
+		Nodes:        logic.GetHostNodes(host),
 		ServerConfig: serverConf,
 		Peers:        hPU.Peers,
 		PeerIDs:      hPU.PeerIDs,
@@ -172,13 +171,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 	}
 
 	newHost := newHostData.ConvertAPIHostToNMHost(currHost)
-	// check if relay information is changed
-	updateRelay := false
-	if newHost.IsRelay && len(newHost.RelayedHosts) > 0 {
-		if len(newHost.RelayedHosts) != len(currHost.RelayedHosts) || !reflect.DeepEqual(newHost.RelayedHosts, currHost.RelayedHosts) {
-			updateRelay = true
-		}
-	}
 
 	logic.UpdateHost(newHost, currHost) // update the in memory struct values
 	if err = logic.UpsertHost(newHost); err != nil {
@@ -186,9 +178,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	if updateRelay {
-		logic.UpdateHostRelay(currHost.ID.String(), currHost.RelayedHosts, newHost.RelayedHosts)
-	}
 	// publish host update through MQ
 	if err := mq.HostUpdate(&models.HostUpdate{
 		Action: models.UpdateHost,
@@ -242,33 +231,6 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	if currHost.IsRelay {
-		if _, _, err := logic.DeleteHostRelay(hostid); err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to dissociate host from relays:", err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-	}
-	if currHost.IsRelayed {
-		relayHost, err := logic.GetHost(currHost.RelayedBy)
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to fetch relay host:", err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-		newRelayedHosts := make([]string, 0)
-		for _, relayedHostID := range relayHost.RelayedHosts {
-			if relayedHostID != hostid {
-				newRelayedHosts = append(newRelayedHosts, relayedHostID)
-			}
-		}
-		relayHost.RelayedHosts = newRelayedHosts
-		if err := logic.UpsertHost(relayHost); err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to update host relays:", err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-	}
 	if err = logic.RemoveHost(currHost); err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

+ 48 - 0
controllers/network.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
+	"golang.org/x/exp/slog"
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -23,6 +24,7 @@ func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	// ACLs
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
@@ -282,3 +284,49 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(network)
 }
+
+// swagger:route PUT /api/networks networks updateNetwork
+//
+// Update pro settings for a network.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: networkBodyResponse
+func updateNetwork(w http.ResponseWriter, r *http.Request) {
+
+	w.Header().Set("Content-Type", "application/json")
+
+	var payload models.Network
+
+	// we decode our body request params
+	err := json.NewDecoder(r.Body).Decode(&payload)
+	if err != nil {
+		slog.Info("error decoding request body", "user", r.Header.Get("user"), "err", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	netOld1, err := logic.GetNetwork(payload.NetID)
+	if err != nil {
+		slog.Info("error fetching network", "user", r.Header.Get("user"), "err", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	// partial update
+	netOld2 := netOld1
+	netOld2.ProSettings = payload.ProSettings
+	_, _, _, _, _, err = logic.UpdateNetwork(&netOld1, &netOld2)
+	if err != nil {
+		slog.Info("failed to update network", "user", r.Header.Get("user"), "err", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	slog.Info("updated network", "network", payload.NetID, "user", r.Header.Get("user"))
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(payload)
+}

+ 33 - 50
controllers/node.go

@@ -23,18 +23,16 @@ var hostIDHeader = "host-id"
 
 func nodeHandlers(r *mux.Router) {
 
-	r.HandleFunc("/api/nodes", authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet)
-	r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/nodes", Authorize(false, false, "user", http.HandlerFunc(getAllNodes))).Methods(http.MethodGet)
+	r.HandleFunc("/api/nodes/{network}", Authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, http.HandlerFunc(createIngressGateway))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
@@ -154,7 +152,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 // even if it's technically ok
 // This is kind of a poor man's RBAC. There's probably a better/smarter way.
 // TODO: Consider better RBAC implementations
-func authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
+func Authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 			Code: http.StatusForbidden, Message: logic.Forbidden_Msg,
@@ -633,12 +631,12 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	}
 	newNode := newData.ConvertToServerNode(&currentNode)
 	relayupdate := false
-	if currentNode.IsRelay && len(newNode.RelayAddrs) > 0 {
-		if len(newNode.RelayAddrs) != len(currentNode.RelayAddrs) {
+	if servercfg.Is_EE && newNode.IsRelay && len(newNode.RelayedNodes) > 0 {
+		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
 			relayupdate = true
 		} else {
-			for i, addr := range newNode.RelayAddrs {
-				if addr != currentNode.RelayAddrs[i] {
+			for i, node := range newNode.RelayedNodes {
+				if node != currentNode.RelayedNodes[i] {
 					relayupdate = true
 				}
 			}
@@ -651,10 +649,6 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	relayedUpdate := false
-	if currentNode.IsRelayed && (currentNode.Address.String() != newNode.Address.String() || currentNode.Address6.String() != newNode.Address6.String()) {
-		relayedUpdate = true
-	}
 	ifaceDelta := logic.IfaceDelta(&currentNode, newNode)
 	aclUpdate := currentNode.DefaultACL != newNode.DefaultACL
 	if ifaceDelta && servercfg.Is_EE {
@@ -671,16 +665,13 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	if relayupdate {
-		updatenodes := logic.UpdateRelay(currentNode.Network, currentNode.RelayAddrs, newNode.RelayAddrs)
+		updatenodes := logic.UpdateRelayed(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes)
 		if len(updatenodes) > 0 {
 			for _, relayedNode := range updatenodes {
 				runUpdates(&relayedNode, false)
 			}
 		}
 	}
-	if relayedUpdate {
-		updateRelay(&currentNode, newNode)
-	}
 	if servercfg.IsDNSMode() {
 		logic.SetDNS()
 	}
@@ -690,8 +681,8 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
 	runUpdates(newNode, ifaceDelta)
-	go func(aclUpdate bool, newNode *models.Node) {
-		if aclUpdate {
+	go func(aclUpdate, relayupdate bool, newNode *models.Node) {
+		if aclUpdate || relayupdate {
 			if err := mq.PublishPeerUpdate(); err != nil {
 				logger.Log(0, "error during node ACL update for node", newNode.ID.String())
 			}
@@ -699,7 +690,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
 			logger.Log(1, "failed to publish dns update", err.Error())
 		}
-	}(aclUpdate, newNode)
+	}(aclUpdate, relayupdate, newNode)
 }
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
@@ -743,6 +734,22 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 	}
+	if node.IsRelayed {
+		// cleanup node from relayednodes on relay node
+		relayNode, err := logic.GetNodeByID(node.RelayedBy)
+		if err == nil {
+			relayedNodes := []string{}
+			for _, relayedNodeID := range relayNode.RelayedNodes {
+				if relayedNodeID == node.ID.String() {
+					continue
+				}
+				relayedNodes = append(relayedNodes, relayedNodeID)
+			}
+			relayNode.RelayedNodes = relayedNodes
+			logic.UpsertNode(&relayNode)
+		}
+
+	}
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	if !fromNode { // notify node change
@@ -778,30 +785,6 @@ func runUpdates(node *models.Node, ifaceDelta bool) {
 	}()
 }
 
-func updateRelay(oldnode, newnode *models.Node) {
-	relay := logic.FindRelay(oldnode)
-	newrelay := relay
-	//check if node's address has been updated and if so, update the relayAddrs of the relay node with the updated address of the relayed node
-	if oldnode.Address.String() != newnode.Address.String() {
-		for i, ip := range newrelay.RelayAddrs {
-			if ip == oldnode.Address.IP.String() {
-				newrelay.RelayAddrs = append(newrelay.RelayAddrs[:i], relay.RelayAddrs[i+1:]...)
-				newrelay.RelayAddrs = append(newrelay.RelayAddrs, newnode.Address.IP.String())
-			}
-		}
-	}
-	//check if node's address(v6) has been updated and if so, update the relayAddrs of the relay node with the updated address(v6) of the relayed node
-	if oldnode.Address6.String() != newnode.Address6.String() {
-		for i, ip := range newrelay.RelayAddrs {
-			if ip == oldnode.Address.IP.String() {
-				newrelay.RelayAddrs = append(newrelay.RelayAddrs[:i], newrelay.RelayAddrs[i+1:]...)
-				newrelay.RelayAddrs = append(newrelay.RelayAddrs, newnode.Address6.IP.String())
-			}
-		}
-	}
-	logic.UpdateNode(relay, newrelay)
-}
-
 func doesUserOwnNode(username, network, nodeID string) bool {
 	u, err := logic.GetUser(username)
 	if err != nil {

+ 0 - 199
controllers/relay.go

@@ -1,199 +0,0 @@
-package controller
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/gorilla/mux"
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/mq"
-)
-
-// swagger:route POST /api/nodes/{network}/{nodeid}/createrelay nodes createRelay
-//
-// Create a relay.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: nodeResponse
-func createRelay(w http.ResponseWriter, r *http.Request) {
-	var relay models.RelayRequest
-	var params = mux.Vars(r)
-	w.Header().Set("Content-Type", "application/json")
-	err := json.NewDecoder(r.Body).Decode(&relay)
-	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
-	}
-	relay.NetID = params["network"]
-	relay.NodeID = params["nodeid"]
-	updatenodes, node, err := logic.CreateRelay(relay)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to create relay on node [%s] on network [%s]: %v", relay.NodeID, relay.NetID, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
-	logger.Log(1, r.Header.Get("user"), "created relay on node", relay.NodeID, "on network", relay.NetID)
-	for _, relayedNode := range updatenodes {
-
-		err = mq.NodeUpdate(&relayedNode)
-		if err != nil {
-			logger.Log(1, "error sending update to relayed node ", relayedNode.ID.String(), "on network", relay.NetID, ": ", err.Error())
-		}
-	}
-
-	apiNode := node.ConvertToAPINode()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-	runUpdates(&node, true)
-}
-
-// swagger:route DELETE /api/nodes/{network}/{nodeid}/deleterelay nodes deleteRelay
-//
-// Remove a relay.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: nodeResponse
-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 := 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, "badrequest"))
-		return
-	}
-	logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid)
-	for _, relayedNode := range updatenodes {
-		err = mq.NodeUpdate(&relayedNode)
-		if err != nil {
-			logger.Log(1, "error sending update to relayed node ", relayedNode.ID.String(), "on network", netid, ": ", err.Error())
-		}
-	}
-	apiNode := node.ConvertToAPINode()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiNode)
-	runUpdates(&node, true)
-}
-
-// swagger:route POST /api/hosts/{hostid}/relay hosts createHostRelay
-//
-// Create a relay.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: nodeResponse
-func createHostRelay(w http.ResponseWriter, r *http.Request) {
-	var relay models.HostRelayRequest
-	var params = mux.Vars(r)
-	w.Header().Set("Content-Type", "application/json")
-	err := json.NewDecoder(r.Body).Decode(&relay)
-	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
-	}
-	relay.HostID = params["hostid"]
-	relayHost, relayedHosts, err := logic.CreateHostRelay(relay)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to create relay on host [%s]: %v", relay.HostID, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
-	if err := mq.HostUpdate(&models.HostUpdate{
-		Action: models.UpdateHost,
-		Host:   *relayHost,
-	}); err != nil {
-		logger.Log(0, "failed to send host update: ", relayHost.ID.String(), err.Error())
-	}
-	logger.Log(1, r.Header.Get("user"), "created relay on host", relay.HostID)
-	go func(relayHostID string) {
-		for _, relayedHost := range relayedHosts {
-			relayedHost.ProxyEnabled = true
-			logic.UpsertHost(&relayedHost)
-			if err := mq.HostUpdate(&models.HostUpdate{
-				Action: models.UpdateHost,
-				Host:   relayedHost,
-			}); err != nil {
-				logger.Log(0, "failed to send host update: ", relayedHost.ID.String(), err.Error())
-			}
-		}
-		if err := mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "fail to publish peer update: ", err.Error())
-		}
-
-	}(relay.HostID)
-
-	apiHostData := relayHost.ConvertNMHostToAPI()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiHostData)
-}
-
-// swagger:route DELETE /api/hosts/{hostid}/relay hosts deleteHostRelay
-//
-// Remove a relay.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: nodeResponse
-func deleteHostRelay(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/json")
-	var params = mux.Vars(r)
-	hostid := params["hostid"]
-	relayHost, relayed, err := logic.DeleteHostRelay(hostid)
-	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 host", hostid)
-	go func() {
-		if err := mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "fail to publish peer update: ", err.Error())
-		}
-		if err := mq.HostUpdate(&models.HostUpdate{
-			Action: models.UpdateHost,
-			Host:   *relayHost,
-		}); err != nil {
-			logger.Log(0, "failed to send host update: ", relayHost.Name, err.Error())
-		}
-		for _, relayedHost := range relayed {
-			if err := mq.HostUpdate(&models.HostUpdate{
-				Action: models.UpdateHost,
-				Host:   relayedHost,
-			}); err != nil {
-				logger.Log(0, "failed to send host update: ", relayedHost.Name, err.Error())
-			}
-		}
-	}()
-	apiHostData := relayHost.ConvertNMHostToAPI()
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(apiHostData)
-}

+ 2 - 2
controllers/server.go

@@ -20,9 +20,9 @@ func serverHandlers(r *mux.Router) {
 		resp.Write([]byte("Server is up and running!!"))
 	}))
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/getserverinfo", authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/usage", authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
 }
 func getUsage(w http.ResponseWriter, r *http.Request) {
 	type usage struct {

+ 97 - 0
ee/ee_controllers/relay.go

@@ -0,0 +1,97 @@
+package ee_controllers
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"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 EE Relays
+func RelayHandlers(r *mux.Router) {
+
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", controller.Authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", controller.Authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
+}
+
+// swagger:route POST /api/nodes/{network}/{nodeid}/createrelay nodes createRelay
+//
+// Create a relay.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+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 := logic.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
+	}
+	go mq.PublishPeerUpdate()
+	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)
+}
+
+// swagger:route DELETE /api/nodes/{network}/{nodeid}/deleterelay nodes deleteRelay
+//
+// Remove a relay.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+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 := 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, "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())
+
+			}
+		}
+		mq.PublishPeerUpdate()
+	}()
+	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 - 0
ee/initialize.go

@@ -23,6 +23,7 @@ func InitEE() {
 		ee_controllers.MetricHandlers,
 		ee_controllers.NetworkUsersHandlers,
 		ee_controllers.UserGroupsHandlers,
+		ee_controllers.RelayHandlers,
 	)
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==

+ 9 - 9
go.mod

@@ -4,22 +4,22 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.14.0
+	github.com/go-playground/validator/v10 v10.14.1
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/lib/pq v1.10.9
-	github.com/mattn/go-sqlite3 v1.14.16
+	github.com/mattn/go-sqlite3 v1.14.17
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.8.3
+	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.4.0
-	golang.org/x/crypto v0.9.0
-	golang.org/x/net v0.10.0 // indirect
-	golang.org/x/oauth2 v0.8.0
-	golang.org/x/sys v0.8.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/crypto v0.10.0
+	golang.org/x/net v0.11.0 // indirect
+	golang.org/x/oauth2 v0.9.0
+	golang.org/x/sys v0.9.0 // indirect
+	golang.org/x/text v0.10.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -36,7 +36,7 @@ require (
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/pkg/errors v0.9.1
-	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
+	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
 	gortc.io/stun v1.23.0
 )
 

+ 18 - 18
go.sum

@@ -30,8 +30,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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
-github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
+github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -71,8 +71,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
-github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
 github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
 github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
@@ -110,8 +110,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
-github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/txn2/txeh v1.4.0 h1:0tdvpA4HGJrj8X3kmrU6o/JFStI009nKxwDpMK5CnRU=
 github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -121,10 +121,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
-golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
+golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -133,10 +133,10 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
-golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
+golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -152,8 +152,8 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -161,8 +161,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
+golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

+ 6 - 0
logic/gateway.go

@@ -102,6 +102,9 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if err != nil {
 		return models.Node{}, err
 	}
+	if node.IsRelayed {
+		return models.Node{}, errors.New("ingress cannot be created on a relayed node")
+	}
 	host, err := GetHost(node.HostID.String())
 	if err != nil {
 		return models.Node{}, err
@@ -112,6 +115,9 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if host.FirewallInUse == models.FIREWALL_NONE {
 		return models.Node{}, errors.New("firewall is not supported for ingress gateways")
 	}
+	if host.NatType != models.NAT_Types.Public {
+		return models.Node{}, errors.New("ingress cannot be created on nodes behind NAT")
+	}
 
 	network, err := GetParentNetwork(netid)
 	if err != nil {

+ 1 - 0
logic/hosts.go

@@ -118,6 +118,7 @@ func CreateHost(h *models.Host) error {
 	}
 	h.HostPass = string(hash)
 	h.AutoUpdate = servercfg.AutoUpdateEnabled()
+	h.EndpointDetection = servercfg.EndpointDetectionEnabled()
 	// if another server has already updated proxyenabled, leave it alone
 	if !h.ProxyEnabledSet {
 		log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)

+ 26 - 44
logic/nodes.go

@@ -41,6 +41,18 @@ func GetNetworkNodes(network string) ([]models.Node, error) {
 	return GetNetworkNodesMemory(allnodes, network), nil
 }
 
+// GetHostNodes - fetches all nodes part of the host
+func GetHostNodes(host *models.Host) []models.Node {
+	nodes := []models.Node{}
+	for _, nodeID := range host.Nodes {
+		node, err := GetNodeByID(nodeID)
+		if err == nil {
+			nodes = append(nodes, node)
+		}
+	}
+	return nodes
+}
+
 // GetNetworkNodesMemory - gets all nodes belonging to a network from list in memory
 func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
 	var nodes = []models.Node{}
@@ -63,6 +75,16 @@ func UpdateNodeCheckin(node *models.Node) error {
 	return database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
 }
 
+// UpsertNode - updates node in the DB
+func UpsertNode(newNode *models.Node) error {
+	newNode.SetLastModified()
+	data, err := json.Marshal(newNode)
+	if err != nil {
+		return err
+	}
+	return database.Insert(newNode.ID.String(), string(data), database.NODES_TABLE_NAME)
+}
+
 // UpdateNode - takes a node and updates another node with it's values
 func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 	if newNode.Address.IP.String() != currentNode.Address.IP.String() {
@@ -73,7 +95,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 		}
 	}
 	nodeACLDelta := currentNode.DefaultACL != newNode.DefaultACL
-	newNode.Fill(currentNode)
+	newNode.Fill(currentNode, servercfg.Is_EE)
 
 	// check for un-settable server values
 	if err := ValidateNode(newNode, true); err != nil {
@@ -326,34 +348,6 @@ func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node,
 	return node, nil
 }
 
-// GetNodeRelay - gets the relay node of a given network
-func GetNodeRelay(network string, relayedNodeAddr string) (models.Node, error) {
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	var relay models.Node
-	if err != nil {
-		if database.IsEmptyRecord(err) {
-			return relay, nil
-		}
-		logger.Log(2, err.Error())
-		return relay, err
-	}
-	for _, value := range collection {
-		err := json.Unmarshal([]byte(value), &relay)
-		if err != nil {
-			logger.Log(2, err.Error())
-			continue
-		}
-		if relay.IsRelay {
-			for _, addr := range relay.RelayAddrs {
-				if addr == relayedNodeAddr {
-					return relay, nil
-				}
-			}
-		}
-	}
-	return relay, errors.New(RELAY_NODE_ERR + " " + relayedNodeAddr)
-}
-
 func GetNodeByID(uuid string) (models.Node, error) {
 	var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
 	if err != nil {
@@ -387,24 +381,12 @@ func GetDeletedNodeByID(uuid string) (models.Node, error) {
 
 // FindRelay - returns the node that is the relay for a relayed node
 func FindRelay(node *models.Node) *models.Node {
-	if !node.IsRelayed {
-		return nil
-	}
-	peers, err := GetNetworkNodes(node.Network)
+	relay, err := GetNodeByID(node.RelayedBy)
 	if err != nil {
+		logger.Log(0, "FindRelay: "+err.Error())
 		return nil
 	}
-	for _, peer := range peers {
-		if !peer.IsRelay {
-			continue
-		}
-		for _, ip := range peer.RelayAddrs {
-			if ip == node.Address.IP.String() || ip == node.Address6.IP.String() {
-				return &peer
-			}
-		}
-	}
-	return nil
+	return &relay
 }
 
 // GetNetworkIngresses - gets the gateways of a network

+ 101 - 104
logic/peers.go

@@ -13,6 +13,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slices"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
@@ -29,41 +30,6 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 		Action: models.ProxyUpdate,
 	}
 	peerConfMap := make(map[string]models.PeerConf)
-	if host.IsRelayed {
-		relayHost, err := GetHost(host.RelayedBy)
-		if err == nil {
-			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
-			if err != nil {
-				logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
-			}
-			proxyPayload.IsRelayed = true
-			proxyPayload.RelayedTo = relayEndpoint
-		} else {
-			logger.Log(0, "couldn't find relay host for:  ", host.ID.String())
-		}
-	}
-	if host.IsRelay {
-		relayedHosts := GetRelayedHosts(host)
-		relayPeersMap := make(map[string]models.RelayedConf)
-		for _, relayedHost := range relayedHosts {
-			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil, nil)
-			if err == nil {
-				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
-				if udpErr == nil {
-					relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{
-						RelayedPeerEndpoint: relayedEndpoint,
-						RelayedPeerPubKey:   relayedHost.PublicKey.String(),
-						Peers:               payload.Peers,
-					}
-				}
-
-			}
-		}
-		proxyPayload.IsRelay = true
-		proxyPayload.RelayedPeerConf = relayPeersMap
-
-	}
 	var ingressStatus bool
 	for _, nodeID := range host.Nodes {
 
@@ -101,18 +67,6 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 				}
 			}
 
-			if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() {
-				relayHost, err := GetHost(peerHost.RelayedBy)
-				if err == nil {
-					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
-					if err == nil {
-						currPeerConf.IsRelayed = true
-						currPeerConf.RelayedTo = relayTo
-					}
-
-				}
-			}
-
 			peerConfMap[peerHost.PublicKey.String()] = currPeerConf
 		}
 		if node.IsIngressGateway {
@@ -167,7 +121,10 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 		HostNetworkInfo: models.HostInfoMap{},
 	}
 
-	logger.Log(1, "peer update for host", host.ID.String())
+	// endpoint detection always comes from the server
+	hostPeerUpdate.Host.EndpointDetection = servercfg.EndpointDetectionEnabled()
+
+	slog.Debug("peer update for host", "hostId", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
 		nodeID := nodeID
@@ -195,58 +152,17 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					//skip yourself
 					continue
 				}
-				var peerConfig wgtypes.PeerConfig
+
 				peerHost, err := GetHost(peer.HostID.String())
 				if err != nil {
 					logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
 					return models.HostPeerUpdate{}, err
 				}
-
-				peerConfig.PublicKey = peerHost.PublicKey
-				peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
-				peerConfig.ReplaceAllowedIPs = true
-				uselocal := false
-				if host.EndpointIP.String() == peerHost.EndpointIP.String() {
-					// peer is on same network
-					// set to localaddress
-					uselocal = true
-					if node.LocalAddress.IP == nil {
-						// use public endpint
-						uselocal = false
-					}
-					if node.LocalAddress.String() == peer.LocalAddress.String() {
-						uselocal = false
-					}
-				}
-				peerConfig.Endpoint = &net.UDPAddr{
-					IP:   peerHost.EndpointIP,
-					Port: getPeerWgListenPort(peerHost),
+				peerConfig := wgtypes.PeerConfig{
+					PublicKey:                   peerHost.PublicKey,
+					PersistentKeepaliveInterval: &peer.PersistentKeepalive,
+					ReplaceAllowedIPs:           true,
 				}
-
-				if uselocal {
-					peerConfig.Endpoint.IP = peer.LocalAddress.IP
-					peerConfig.Endpoint.Port = peerHost.ListenPort
-				}
-				allowedips := GetAllowedIPs(&node, &peer, nil)
-				if peer.IsIngressGateway {
-					for _, entry := range peer.IngressGatewayRange {
-						_, cidr, err := net.ParseCIDR(string(entry))
-						if err == nil {
-							allowedips = append(allowedips, *cidr)
-						}
-					}
-				}
-				if peer.IsEgressGateway {
-					allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
-				}
-				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())) &&
-					(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
-					peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
-				}
-
 				if node.IsIngressGateway || node.IsEgressGateway {
 					if peer.IsIngressGateway {
 						_, extPeerIDAndAddrs, err := getExtPeers(&peer)
@@ -279,6 +195,47 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 						ID:      peer.ID.String(),
 					}
 				}
+				if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || (peer.IsRelayed && peer.RelayedBy != node.ID.String()) {
+					// if node is relayed and peer is not the relay, set remove to true
+					if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; ok {
+						continue
+					}
+					peerConfig.Remove = true
+					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
+					continue
+				}
+
+				uselocal := false
+				if host.EndpointIP.String() == peerHost.EndpointIP.String() {
+					// peer is on same network
+					// set to localaddress
+					uselocal = true
+					if node.LocalAddress.IP == nil {
+						// use public endpint
+						uselocal = false
+					}
+					if node.LocalAddress.String() == peer.LocalAddress.String() {
+						uselocal = false
+					}
+				}
+				peerConfig.Endpoint = &net.UDPAddr{
+					IP:   peerHost.EndpointIP,
+					Port: getPeerWgListenPort(peerHost),
+				}
+
+				if uselocal {
+					peerConfig.Endpoint.IP = peer.LocalAddress.IP
+					peerConfig.Endpoint.Port = peerHost.ListenPort
+				}
+				allowedips := GetAllowedIPs(&node, &peer, nil)
+				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())) &&
+					(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
+					peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
+				}
 
 				peerProxyPort := GetProxyListenPort(peerHost)
 				var nodePeer wgtypes.PeerConfig
@@ -300,8 +257,9 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					nodePeer = peerConfig
 				} else {
 					peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
-					peerAllowedIPs = append(peerAllowedIPs, allowedips...)
+					peerAllowedIPs = append(peerAllowedIPs, peerConfig.AllowedIPs...)
 					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
+					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 						ID:              peer.ID.String(),
 						Address:         peer.PrimaryAddress(),
@@ -624,14 +582,15 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
 			}
 		}
 	}
+	if node.IsRelayed && node.RelayedBy == peer.ID.String() {
+		allowedips = append(allowedips, getAllowedIpsForRelayed(node, peer)...)
+
+	}
 	return allowedips
 }
 
-func getEgressIPs(node, peer *models.Node) []net.IPNet {
-	host, err := GetHost(node.HostID.String())
-	if err != nil {
-		logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
-	}
+func getEgressIPs(peer *models.Node) []net.IPNet {
+
 	peerHost, err := GetHost(peer.HostID.String())
 	if err != nil {
 		logger.Log(0, "error retrieving host for peer", peer.ID.String(), err.Error())
@@ -651,12 +610,12 @@ func getEgressIPs(node, peer *models.Node) []net.IPNet {
 		}
 		// getting the public ip of node
 		if ipnet.Contains(peerHost.EndpointIP) && !internetGateway { // ensuring egress gateway range does not contain endpoint of node
-			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", host.EndpointIP.String(), ", omitting")
+			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", peerHost.EndpointIP.String(), ", omitting")
 			continue // skip adding egress range if overlaps with node's ip
 		}
 		// TODO: Could put in a lot of great logic to avoid conflicts / bad routes
-		if ipnet.Contains(node.LocalAddress.IP) && !internetGateway { // ensuring egress gateway range does not contain public ip of node
-			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", node.LocalAddress.String(), ", omitting")
+		if ipnet.Contains(peer.LocalAddress.IP) && !internetGateway { // ensuring egress gateway range does not contain public ip of node
+			logger.Log(2, "egress IP range of ", iprange, " overlaps with ", peer.LocalAddress.String(), ", omitting")
 			continue // skip adding egress range if overlaps with node's local ip
 		}
 		if err != nil {
@@ -687,12 +646,50 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet {
 	// handle egress gateway peers
 	if peer.IsEgressGateway {
 		//hasGateway = true
-		egressIPs := getEgressIPs(node, peer)
+		egressIPs := getEgressIPs(peer)
 		allowedips = append(allowedips, egressIPs...)
 	}
+	if peer.IsRelay {
+		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
 }
 
+// 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
+	}
+	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
+}
+
 func getCIDRMaskFromAddr(addr string) net.IPMask {
 	cidr := net.CIDRMask(32, 32)
 	ipAddr, err := netip.ParseAddr(addr)

+ 89 - 145
logic/relay.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"time"
+	"net"
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -31,8 +31,7 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
 		return returnnodes, models.Node{}, err
 	}
 	node.IsRelay = true
-	node.RelayAddrs = relay.RelayAddrs
-
+	node.RelayedNodes = relay.RelayedNodes
 	node.SetLastModified()
 	nodeData, err := json.Marshal(&node)
 	if err != nil {
@@ -41,144 +40,98 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
 	if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
 		return returnnodes, models.Node{}, err
 	}
-	returnnodes, err = SetRelayedNodes(true, node.Network, node.RelayAddrs)
-	if err != nil {
-		return returnnodes, node, err
-	}
-	return returnnodes, node, nil
-}
-
-// CreateHostRelay - creates a host relay
-func CreateHostRelay(relay models.HostRelayRequest) (relayHost *models.Host, relayedHosts []models.Host, err error) {
-
-	relayHost, err = GetHost(relay.HostID)
-	if err != nil {
-		return
-	}
-	err = validateHostRelay(relay)
-	if err != nil {
-		return
-	}
-	relayHost.IsRelay = true
-	relayHost.ProxyEnabled = true
-	relayHost.RelayedHosts = relay.RelayedHosts
-	err = UpsertHost(relayHost)
-	if err != nil {
-		return
-	}
-	relayedHosts = SetRelayedHosts(true, relay.HostID, relay.RelayedHosts)
-	return
-}
-
-// SetRelayedHosts - updates the relayed hosts status
-func SetRelayedHosts(setRelayed bool, relayHostID string, relayedHostIDs []string) []models.Host {
-	var relayedHosts []models.Host
-	for _, relayedHostID := range relayedHostIDs {
-		host, err := GetHost(relayedHostID)
-		if err == nil {
-			if setRelayed {
-				host.IsRelayed = true
-				host.RelayedBy = relayHostID
-				host.ProxyEnabled = true
-			} else {
-				host.IsRelayed = false
-				host.RelayedBy = ""
-			}
-			err = UpsertHost(host)
-			if err == nil {
-				relayedHosts = append(relayedHosts, *host)
-			}
+	returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes)
+	for _, relayedNode := range returnnodes {
+		data, err := json.Marshal(&relayedNode)
+		if err != nil {
+			logger.Log(0, "marshalling relayed node", err.Error())
+			continue
+		}
+		if err := database.Insert(relayedNode.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
+			logger.Log(0, "inserting relayed node", err.Error())
+			continue
 		}
 	}
-	return relayedHosts
+	return returnnodes, node, nil
 }
 
-// SetRelayedNodes- set relayed nodes
-func SetRelayedNodes(setRelayed bool, networkName string, addrs []string) ([]models.Node, error) {
+// SetRelayedNodes- sets and saves node as relayed
+func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node {
 	var returnnodes []models.Node
-	networkNodes, err := GetNetworkNodes(networkName)
-	if err != nil {
-		return returnnodes, err
-	}
-	for _, node := range networkNodes {
-		for _, addr := range addrs {
-			if addr == node.Address.IP.String() || addr == node.Address6.IP.String() {
-				if setRelayed {
-					node.IsRelayed = true
-				} else {
-					node.IsRelayed = false
-				}
-				data, err := json.Marshal(&node)
-				if err != nil {
-					return returnnodes, err
-				}
-				database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
-				returnnodes = append(returnnodes, node)
-			}
+	for _, id := range relayed {
+		node, err := GetNodeByID(id)
+		if err != nil {
+			logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error())
+			continue
 		}
-	}
-	return returnnodes, nil
-}
-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)
-			}
+		node.IsRelayed = setRelayed
+		if node.IsRelayed {
+			node.RelayedBy = relay
+		} else {
+			node.RelayedBy = ""
 		}
-	}
-	return returnnodes, nil
-}
-
-// GetRelayedHosts - gets the relayed hosts of a relay host
-func GetRelayedHosts(relayHost *models.Host) []models.Host {
-	relayedHosts := []models.Host{}
-
-	for _, hostID := range relayHost.RelayedHosts {
-		relayedHost, err := GetHost(hostID)
-		if err == nil {
-			relayedHosts = append(relayedHosts, *relayedHost)
+		node.SetLastModified()
+		data, err := json.Marshal(&node)
+		if err != nil {
+			logger.Log(0, "setRelayedNodes.Marshal", err.Error())
+			continue
 		}
+		if err := database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME); err != nil {
+			logger.Log(0, "setRelayedNodes.Insert", err.Error())
+			continue
+		}
+		returnnodes = append(returnnodes, node)
 	}
-	return relayedHosts
+	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) error {
 	var err error
 	//isIp := functions.IsIpCIDR(gateway.RangeString)
-	empty := len(relay.RelayAddrs) == 0
+	empty := len(relay.RelayedNodes) == 0
 	if empty {
-		err = errors.New("IP Ranges Cannot Be Empty")
-	}
-	return err
-}
-
-func validateHostRelay(relay models.HostRelayRequest) error {
-	if len(relay.RelayedHosts) == 0 {
-		return errors.New("relayed hosts are empty")
+		return errors.New("IP Ranges Cannot Be Empty")
 	}
-	return nil
-}
-
-// UpdateRelay - updates a relay
-func UpdateRelay(network string, oldAddrs []string, newAddrs []string) []models.Node {
-	var returnnodes []models.Node
-	time.Sleep(time.Second / 4)
-	_, err := SetRelayedNodes(false, network, oldAddrs)
+	node, err := GetNodeByID(relay.NodeID)
 	if err != nil {
-		logger.Log(1, err.Error())
+		return err
 	}
-	returnnodes, err = SetRelayedNodes(true, network, newAddrs)
-	if err != nil {
-		logger.Log(1, err.Error())
+	if node.IsRelay {
+		return errors.New("node is already acting as a relay")
 	}
-	return returnnodes
+	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 + ")")
+		}
+	}
+	return err
+}
+
+// UpdateRelayed - updates relay nodes
+func UpdateRelayed(relay string, oldNodes []string, newNodes []string) []models.Node {
+	_ = SetRelayedNodes(false, relay, oldNodes)
+	return SetRelayedNodes(true, relay, newNodes)
 }
 
 // DeleteRelay - deletes a relay
@@ -188,15 +141,10 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	if err != nil {
 		return returnnodes, models.Node{}, err
 	}
-	returnnodes, err = SetRelayedNodes(false, node.Network, node.RelayAddrs)
-	if err != nil {
-		return returnnodes, node, err
-	}
-
+	returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes)
 	node.IsRelay = false
-	node.RelayAddrs = []string{}
+	node.RelayedNodes = []string{}
 	node.SetLastModified()
-
 	data, err := json.Marshal(&node)
 	if err != nil {
 		return returnnodes, models.Node{}, err
@@ -207,24 +155,20 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	return returnnodes, node, nil
 }
 
-// DeleteHostRelay - removes host as relay
-func DeleteHostRelay(relayHostID string) (relayHost *models.Host, relayedHosts []models.Host, err error) {
-	relayHost, err = GetHost(relayHostID)
+func getRelayedAddresses(id string) []net.IPNet {
+	addrs := []net.IPNet{}
+	node, err := GetNodeByID(id)
 	if err != nil {
-		return
+		logger.Log(0, "getRelayedAddresses: "+err.Error())
+		return addrs
 	}
-	relayedHosts = SetRelayedHosts(false, relayHostID, relayHost.RelayedHosts)
-	relayHost.IsRelay = false
-	relayHost.RelayedHosts = []string{}
-	err = UpsertHost(relayHost)
-	if err != nil {
-		return
+	if node.Address.IP != nil {
+		node.Address.Mask = net.CIDRMask(32, 32)
+		addrs = append(addrs, node.Address)
 	}
-	return
-}
-
-// UpdateHostRelay - updates the relay host with new relayed hosts
-func UpdateHostRelay(relayHostID string, oldRelayedHosts, newRelayedHosts []string) {
-	_ = SetRelayedHosts(false, relayHostID, oldRelayedHosts)
-	_ = SetRelayedHosts(true, relayHostID, newRelayedHosts)
+	if node.Address6.IP != nil {
+		node.Address.Mask = net.CIDRMask(128, 128)
+		addrs = append(addrs, node.Address6)
+	}
+	return addrs
 }

+ 3 - 3
logic/wireguard.go

@@ -29,11 +29,11 @@ func IfaceDelta(currentNode *models.Node, newNode *models.Node) bool {
 		}
 	}
 	if newNode.IsRelay {
-		if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) {
+		if len(currentNode.RelayedNodes) != len(newNode.RelayedNodes) {
 			return true
 		}
-		for _, address := range newNode.RelayAddrs {
-			if !StringSliceContains(currentNode.RelayAddrs, address) {
+		for _, node := range newNode.RelayedNodes {
+			if !StringSliceContains(currentNode.RelayedNodes, node) {
 				return true
 			}
 		}

+ 22 - 1
main.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"os"
 	"os/signal"
+	"path/filepath"
 	"runtime/debug"
 	"sync"
 	"syscall"
@@ -26,9 +27,10 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	stunserver "github.com/gravitl/netmaker/stun-server"
+	"golang.org/x/exp/slog"
 )
 
-var version = "v0.20.1"
+var version = "v0.20.3"
 
 // Start DB Connection and start API Request Handler
 func main() {
@@ -184,6 +186,25 @@ func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
 func setVerbosity() {
 	verbose := int(servercfg.GetVerbosity())
 	logger.Verbosity = verbose
+	logLevel := &slog.LevelVar{}
+	replace := func(groups []string, a slog.Attr) slog.Attr {
+		if a.Key == slog.SourceKey {
+			a.Value = slog.StringValue(filepath.Base(a.Value.String()))
+		}
+		return a
+	}
+	logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace, Level: logLevel}))
+	slog.SetDefault(logger)
+	switch verbose {
+	case 4:
+		logLevel.Set(slog.LevelDebug)
+	case 3:
+		logLevel.Set(slog.LevelInfo)
+	case 2:
+		logLevel.Set(slog.LevelWarn)
+	default:
+		logLevel.Set(slog.LevelError)
+	}
 }
 
 func setGarbageCollection() {

+ 0 - 8
models/api_host.go

@@ -67,10 +67,6 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.Verbosity = h.Verbosity
 	a.Version = h.Version
 	a.IsDefault = h.IsDefault
-	a.IsRelay = h.IsRelay
-	a.RelayedHosts = h.RelayedHosts
-	a.IsRelayed = h.IsRelayed
-	a.RelayedBy = h.RelayedBy
 	return &a
 }
 
@@ -108,10 +104,6 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.Nodes = currentHost.Nodes
 	h.TrafficKeyPublic = currentHost.TrafficKeyPublic
 	h.OS = currentHost.OS
-	h.RelayedBy = a.RelayedBy
-	h.RelayedHosts = a.RelayedHosts
-	h.IsRelay = a.IsRelay
-	h.IsRelayed = a.IsRelayed
 	h.ProxyEnabled = a.ProxyEnabled
 	h.IsDefault = a.IsDefault
 	h.NatType = currentHost.NatType

+ 7 - 3
models/api_node.go

@@ -25,11 +25,12 @@ type ApiNode struct {
 	NetworkRange6           string   `json:"networkrange6"`
 	IsRelayed               bool     `json:"isrelayed"`
 	IsRelay                 bool     `json:"isrelay"`
+	RelayedBy               string   `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
+	RelayedNodes            []string `json:"relaynodes" yaml:"relayedNodes"`
 	IsEgressGateway         bool     `json:"isegressgateway"`
 	IsIngressGateway        bool     `json:"isingressgateway"`
 	EgressGatewayRanges     []string `json:"egressgatewayranges"`
 	EgressGatewayNatEnabled bool     `json:"egressgatewaynatenabled"`
-	RelayAddrs              []string `json:"relayaddrs"`
 	FailoverNode            string   `json:"failovernode"`
 	DNSOn                   bool     `json:"dnson"`
 	IngressDns              string   `json:"ingressdns"`
@@ -53,6 +54,8 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.HostID, _ = uuid.Parse(a.HostID)
 	convertedNode.IsRelay = a.IsRelay
 	convertedNode.IsRelayed = a.IsRelayed
+	convertedNode.RelayedBy = a.RelayedBy
+	convertedNode.RelayedNodes = a.RelayedNodes
 	convertedNode.PendingDelete = a.PendingDelete
 	convertedNode.Failover = a.Failover
 	convertedNode.IsEgressGateway = a.IsEgressGateway
@@ -66,7 +69,7 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest
 	convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled
 	convertedNode.PersistentKeepalive = time.Second * time.Duration(a.PersistentKeepalive)
-	convertedNode.RelayAddrs = a.RelayAddrs
+	convertedNode.RelayedNodes = a.RelayedNodes
 	convertedNode.DefaultACL = a.DefaultACL
 	convertedNode.OwnerID = currentNode.OwnerID
 	_, networkRange, err := net.ParseCIDR(a.NetworkRange)
@@ -140,11 +143,12 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 	}
 	apiNode.IsRelayed = nm.IsRelayed
 	apiNode.IsRelay = nm.IsRelay
+	apiNode.RelayedBy = nm.RelayedBy
+	apiNode.RelayedNodes = nm.RelayedNodes
 	apiNode.IsEgressGateway = nm.IsEgressGateway
 	apiNode.IsIngressGateway = nm.IsIngressGateway
 	apiNode.EgressGatewayRanges = nm.EgressGatewayRanges
 	apiNode.EgressGatewayNatEnabled = nm.EgressGatewayNatEnabled
-	apiNode.RelayAddrs = nm.RelayAddrs
 	apiNode.FailoverNode = nm.FailoverNode.String()
 	if isUUIDSet(apiNode.FailoverNode) {
 		apiNode.FailoverNode = ""

+ 1 - 0
models/host.go

@@ -48,6 +48,7 @@ type Host struct {
 	IPForwarding       bool             `json:"ipforwarding" yaml:"ipforwarding"`
 	DaemonInstalled    bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
 	AutoUpdate         bool             `json:"autoupdate" yaml:"autoupdate"`
+	EndpointDetection  bool             `json:"endpointdetection" yaml:"endpointdetection"`
 	HostPass           string           `json:"hostpass" yaml:"hostpass"`
 	Name               string           `json:"name" yaml:"name"`
 	OS                 string           `json:"os" yaml:"os"`

+ 9 - 8
models/node.go

@@ -69,6 +69,10 @@ type CommonNode struct {
 	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
+	IsRelayed           bool          `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	RelayedBy           string        `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
+	IsRelay             bool          `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
+	RelayedNodes        []string      `json:"relaynodes" yaml:"relayedNodes"`
 	IngressDNS          string        `json:"ingressdns" yaml:"ingressdns"`
 	DNSOn               bool          `json:"dnson" yaml:"dnson"`
 	PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
@@ -86,9 +90,6 @@ type Node struct {
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	IngressGatewayRange6    string               `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
-	IsRelayed               bool                 `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	IsRelay                 bool                 `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
-	RelayAddrs              []string             `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
 	// == PRO ==
 	DefaultACL   string    `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	OwnerID      string    `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
@@ -350,7 +351,7 @@ func (node *LegacyNode) SetDefaultFailover() {
 }
 
 // Node.Fill - fills other node data into calling node data if not set on calling node
-func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftables present
+func (newNode *Node) Fill(currentNode *Node, isEE bool) { // TODO add new field for nftables present
 	newNode.ID = currentNode.ID
 	newNode.HostID = currentNode.HostID
 	// Revisit the logic for boolean values
@@ -401,13 +402,13 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.Action == "" {
 		newNode.Action = currentNode.Action
 	}
-	if newNode.RelayAddrs == nil {
-		newNode.RelayAddrs = currentNode.RelayAddrs
+	if newNode.RelayedNodes == nil {
+		newNode.RelayedNodes = currentNode.RelayedNodes
 	}
-	if newNode.IsRelay != currentNode.IsRelay {
+	if newNode.IsRelay != currentNode.IsRelay && isEE {
 		newNode.IsRelay = currentNode.IsRelay
 	}
-	if newNode.IsRelayed == currentNode.IsRelayed {
+	if newNode.IsRelayed == currentNode.IsRelayed && isEE {
 		newNode.IsRelayed = currentNode.IsRelayed
 	}
 	if newNode.Server == "" {

+ 4 - 3
models/structs.go

@@ -154,9 +154,9 @@ type EgressGatewayRequest struct {
 
 // RelayRequest - relay request struct
 type RelayRequest struct {
-	NodeID     string   `json:"nodeid" bson:"nodeid"`
-	NetID      string   `json:"netid" bson:"netid"`
-	RelayAddrs []string `json:"relayaddrs" bson:"relayaddrs"`
+	NodeID       string   `json:"nodeid"`
+	NetID        string   `json:"netid"`
+	RelayedNodes []string `json:"relayaddrs"`
 }
 
 // HostRelayRequest - struct for host relay creation
@@ -202,6 +202,7 @@ type TrafficKeys struct {
 // HostPull - response of a host's pull
 type HostPull struct {
 	Host         Host                 `json:"host" yaml:"host"`
+	Nodes        []Node               `json:"nodes" yaml:"nodes"`
 	Peers        []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
 	ServerConfig ServerConfig         `json:"server_config" yaml:"server_config"`
 	PeerIDs      PeerMap              `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"`

+ 51 - 52
mq/handlers.go

@@ -10,86 +10,86 @@ import (
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
 // DefaultHandler default message queue handler  -- NOT USED
 func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
-	logger.Log(0, "MQTT Message: Topic: ", string(msg.Topic()), " Message: ", string(msg.Payload()))
+	slog.Info("mqtt default handler", "topic", msg.Topic(), "message", msg.Payload())
 }
 
 // UpdateNode  message Handler -- handles updates from client nodes
 func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	if err != nil {
-		logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
+		slog.Error("error getting node.ID ", "topic", msg.Topic(), "error", err)
 		return
 	}
 	currentNode, err := logic.GetNodeByID(id)
 	if err != nil {
-		logger.Log(1, "error getting node ", id, err.Error())
+		slog.Error("error getting node", "id", id, "error", err)
 		return
 	}
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	if decryptErr != nil {
-		logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
+		slog.Error("failed to decrypt message for node", "id", id, "error", decryptErr)
 		return
 	}
 	var newNode models.Node
 	if err := json.Unmarshal(decrypted, &newNode); err != nil {
-		logger.Log(1, "error unmarshaling payload ", err.Error())
+		slog.Error("error unmarshaling payload", "error", err)
 		return
 	}
 
 	ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
 	if servercfg.Is_EE && ifaceDelta {
 		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
-			logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
+			slog.Warn("failed to reset failover list during node update", "nodeid", currentNode.ID, "network", currentNode.Network)
 		}
 	}
 	newNode.SetLastCheckIn()
 	if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
-		logger.Log(1, "error saving node", err.Error())
+		slog.Error("error saving node", "id", id, "error", err)
 		return
 	}
 	if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
 		if err = PublishPeerUpdate(); err != nil {
-			logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
+			slog.Warn("error updating peers when node informed the server of an interface change", "nodeid", currentNode.ID, "error", err)
 		}
 	}
 
-	logger.Log(1, "updated node", id, newNode.ID.String())
+	slog.Info("updated node", "id", id, "newnodeid", newNode.ID)
 }
 
 // UpdateHost  message Handler -- handles host updates from clients
 func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	if err != nil {
-		logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
+		slog.Error("error getting host.ID sent on ", "topic", msg.Topic(), "error", err)
 		return
 	}
 	currentHost, err := logic.GetHost(id)
 	if err != nil {
-		logger.Log(1, "error getting host ", id, err.Error())
+		slog.Error("error getting host", "id", id, "error", err)
 		return
 	}
 	decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
 	if decryptErr != nil {
-		logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
+		slog.Error("failed to decrypt message for host", "id", id, "error", decryptErr)
 		return
 	}
 	var hostUpdate models.HostUpdate
 	if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
-		logger.Log(1, "error unmarshaling payload ", err.Error())
+		slog.Error("error unmarshaling payload", "error", err)
 		return
 	}
-	logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
+	slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID)
 	var sendPeerUpdate bool
 	switch hostUpdate.Action {
 	case models.CheckIn:
@@ -98,21 +98,21 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		hu := hostactions.GetAction(currentHost.ID.String())
 		if hu != nil {
 			if err = HostUpdate(hu); err != nil {
-				logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+				slog.Error("failed to send new node to host", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err)
 				return
 			} else {
 				if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 					if err = AppendNodeUpdateACL(hu.Host.ID.String(), hu.Node.Network, hu.Node.ID.String(), servercfg.GetServer()); err != nil {
-						logger.Log(0, "failed to add ACLs for EMQX node", err.Error())
+						slog.Error("failed to add ACLs for EMQX node", "error", err)
 						return
 					}
 				}
 				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil, nil); err != nil {
-					logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+					slog.Error("failed peers publish after join acknowledged", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err)
 					return
 				}
 				if err = handleNewNodeDNS(&hu.Host, &hu.Node); err != nil {
-					logger.Log(0, "failed to send dns update after node,", hu.Node.ID.String(), ", added to host", hu.Host.Name, err.Error())
+					slog.Error("failed to send dns update after node added to host", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err)
 					return
 				}
 			}
@@ -131,7 +131,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			}
 			data, err := json.Marshal(&peerUpdate)
 			if err != nil {
-				logger.Log(2, "json error", err.Error())
+				slog.Error("failed to marshal peer update", "error", err)
 			}
 			hosts := logic.GetRelatedHosts(hostUpdate.Host.ID.String())
 			server := servercfg.GetServer()
@@ -143,23 +143,23 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 		err := logic.UpsertHost(currentHost)
 		if err != nil {
-			logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
+			slog.Error("failed to update host", "id", currentHost.ID, "error", err)
 			return
 		}
 	case models.DeleteHost:
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 			// delete EMQX credentials for host
 			if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
-				logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
+				slog.Error("failed to remove host credentials from EMQX", "id", currentHost.ID, "error", err)
 				return
 			}
 		}
 		if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
-			logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
+			slog.Error("failed to delete all nodes of host", "id", currentHost.ID, "error", err)
 			return
 		}
 		if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
-			logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
+			slog.Error("failed to delete host", "id", currentHost.ID, "error", err)
 			return
 		}
 		sendPeerUpdate = true
@@ -167,7 +167,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		if servercfg.IsUsingTurn() {
 			err = logic.RegisterHostWithTurn(hostUpdate.Host.ID.String(), hostUpdate.Host.HostPass)
 			if err != nil {
-				logger.Log(0, "failed to register host with turn server: ", err.Error())
+				slog.Error("failed to register host with turn server", "id", currentHost.ID, "error", err)
 				return
 			}
 		}
@@ -177,7 +177,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 	if sendPeerUpdate {
 		err := PublishPeerUpdate()
 		if err != nil {
-			logger.Log(0, "failed to pulish peer update: ", err.Error())
+			slog.Error("failed to publish peer update", "error", err)
 		}
 	}
 	// if servercfg.Is_EE && ifaceDelta {
@@ -192,57 +192,55 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 	if servercfg.Is_EE {
 		id, err := getID(msg.Topic())
 		if err != nil {
-			logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
+			slog.Error("error getting ID sent on ", "topic", msg.Topic(), "error", err)
 			return
 		}
 		currentNode, err := logic.GetNodeByID(id)
 		if err != nil {
-			logger.Log(1, "error getting node ", id, err.Error())
+			slog.Error("error getting node", "id", id, "error", err)
 			return
 		}
 		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 		if decryptErr != nil {
-			logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
+			slog.Error("failed to decrypt message for node", "id", id, "error", decryptErr)
 			return
 		}
 
 		var newMetrics models.Metrics
 		if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
-			logger.Log(1, "error unmarshaling payload ", err.Error())
+			slog.Error("error unmarshaling payload", "error", err)
 			return
 		}
 
 		shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
 
 		if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
-			logger.Log(1, "faield to update node metrics", id, err.Error())
+			slog.Error("failed to update node metrics", "id", id, "error", err)
 			return
 		}
 		if servercfg.IsMetricsExporter() {
 			if err := pushMetricsToExporter(newMetrics); err != nil {
-				logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v",
-					currentNode.ID, err))
+				slog.Error("failed to push node metrics to exporter", "id", currentNode.ID, "error", err)
 			}
 		}
 
 		if newMetrics.Connectivity != nil {
 			err := logic.EnterpriseFailoverFunc(&currentNode)
 			if err != nil {
-				logger.Log(0, "failed to failover for node", currentNode.ID.String(), "on network", currentNode.Network, "-", err.Error())
+				slog.Error("failed to failover for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
 			}
 		}
 
 		if shouldUpdate {
-			logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
+			slog.Info("updating peers after node detected connectivity issues", "id", currentNode.ID, "network", currentNode.Network)
 			host, err := logic.GetHost(currentNode.HostID.String())
 			if err == nil {
 				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil, nil); err != nil {
-					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
+					slog.Warn("failed to publish update after failover peer change for node", "id", currentNode.ID, "network", currentNode.Network, "error", err)
 				}
 			}
 		}
-
-		logger.Log(1, "updated node metrics", id)
+		slog.Debug("updated node metrics", "id", id)
 	}
 }
 
@@ -250,17 +248,17 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	if err != nil {
-		logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
+		slog.Error("error getting node.ID sent on ", "topic", msg.Topic(), "error", err)
 		return
 	}
 	currentNode, err := logic.GetNodeByID(id)
 	if err != nil {
-		logger.Log(1, "error getting node ", id, err.Error())
+		slog.Error("error getting node", "id", id, "error", err)
 		return
 	}
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	if decryptErr != nil {
-		logger.Log(1, "failed to decrypt message during client peer update for node ", id, decryptErr.Error())
+		slog.Error("failed to decrypt message for node", "id", id, "error", decryptErr)
 		return
 	}
 	switch decrypted[0] {
@@ -268,12 +266,12 @@ func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 		// do we still need this
 	case ncutils.DONE:
 		if err = PublishPeerUpdate(); err != nil {
-			logger.Log(1, "error publishing peer update for node", currentNode.ID.String(), err.Error())
+			slog.Error("error publishing peer update for node", "id", currentNode.ID, "error", err)
 			return
 		}
 	}
 
-	logger.Log(1, "sent peer updates after signal received from", id)
+	slog.Info("sent peer updates after signal received from", "id", id)
 }
 
 func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) bool {
@@ -282,7 +280,7 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 	}
 	oldMetrics, err := logic.GetMetrics(currentNode.ID.String())
 	if err != nil {
-		logger.Log(1, "error finding old metrics for node", currentNode.ID.String())
+		slog.Error("error finding old metrics for node", "id", currentNode.ID, "error", err)
 		return false
 	}
 	if oldMetrics.FailoverPeers == nil {
@@ -349,7 +347,7 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 	// add nodes that need failover
 	nodes, err := logic.GetNetworkNodes(currentNode.Network)
 	if err != nil {
-		logger.Log(0, "failed to retrieve nodes while updating metrics")
+		slog.Error("failed to retrieve nodes while updating metrics", "error", err)
 		return false
 	}
 	for _, node := range nodes {
@@ -415,13 +413,13 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
 				fakeNode.Action = models.NODE_DELETE
 				fakeNode.PendingDelete = true
 				if err := NodeUpdate(&fakeNode); err != nil {
-					logger.Log(0, "failed to inform host", currentHost.Name, currentHost.ID.String(), "to remove node", currNodeID, err.Error())
+					slog.Warn("failed to inform host to remove node", "host", currentHost.Name, "hostid", currentHost.ID, "nodeid", currNodeID, "error", err)
 				}
 			}
 			continue
 		}
 		if err := logic.UpdateNodeCheckin(&node); err != nil {
-			logger.Log(0, "error updating node", node.ID.String(), " on checkin", err.Error())
+			slog.Warn("failed to update node on checkin", "nodeid", node.ID, "error", err)
 		}
 	}
 
@@ -433,26 +431,27 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
 		currentHost.FirewallInUse = h.FirewallInUse
 		currentHost.Version = h.Version
 		if err := logic.UpsertHost(currentHost); err != nil {
-			logger.Log(0, "failed to update host after check-in", h.Name, h.ID.String(), err.Error())
+			slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
 			return false
 		}
 	}
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
-		h.DefaultInterface != currentHost.DefaultInterface
+		h.DefaultInterface != currentHost.DefaultInterface ||
+		h.EndpointDetection != servercfg.EndpointDetectionEnabled()
 	if ifaceDelta { // only save if something changes
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.Interfaces = h.Interfaces
 		currentHost.DefaultInterface = h.DefaultInterface
 		currentHost.NatType = h.NatType
 		if err := logic.UpsertHost(currentHost); err != nil {
-			logger.Log(0, "failed to update host after check-in", h.Name, h.ID.String(), err.Error())
+			slog.Error("failed to update host after check-in", "name", h.Name, "id", h.ID, "error", err)
 			return false
 		}
-		logger.Log(1, "updated host after check-in", currentHost.Name, currentHost.ID.String())
+		slog.Info("updated host after check-in", "name", currentHost.Name, "id", currentHost.ID)
 	}
 
-	logger.Log(2, "check-in processed for host", h.Name, h.ID.String())
+	slog.Info("check-in processed for host", "name", h.Name, "id", h.ID)
 	return ifaceDelta
 }

+ 1 - 1
release.md

@@ -1,5 +1,5 @@
 
-# Netmaker v0.20.1
+# Netmaker v0.20.3
 
 ## whats new
 - 

+ 1 - 0
scripts/netmaker.default.env

@@ -19,6 +19,7 @@ NETMAKER_ACCOUNT_ID=
 LICENSE_KEY=
 SERVER_IMAGE_TAG=
 UI_IMAGE_TAG=
+NETCLIENT_ENDPOINT_DETECTION="disabled"
 # used for HA - identifies this server vs other servers
 NODE_ID="netmaker-server-1"
 METRICS_EXPORTER="off"

+ 15 - 8
scripts/nm-quick.sh

@@ -4,7 +4,7 @@ CONFIG_FILE=netmaker.env
 # location of nm-quick.sh (usually `/root`)
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
-NM_QUICK_VERSION="0.1.0"
+NM_QUICK_VERSION="0.1.1"
 LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
 
 if [ $(id -u) -ne 0 ]; then
@@ -17,11 +17,12 @@ unset BUILD_TYPE
 unset BUILD_TAG
 unset IMAGE_TAG
 unset AUTO_BUILD
+unset NETMAKER_BASE_DOMAIN
 
 # usage - displays usage instructions
 usage() {
 	echo "nm-quick.sh v$NM_QUICK_VERSION"
-	echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]"
+	echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto] [-d domain]"
 	echo "  -e      if specified, will install netmaker EE"
 	echo "  -b      type of build; options:"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
@@ -29,14 +30,16 @@ usage() {
 	echo "          \"branch\": - will install a specific branch using remote git and dockerhub"
 	echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
 	echo "  -a      auto-build; skip prompts and use defaults, if none provided"
+	echo "  -d      domain; if specified, will use this domain instead of auto-generating one"
 	echo "examples:"
 	echo "          nm-quick.sh -e -b version -t $LATEST"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"
 	echo "          nm-quick.sh -e -b branch -t develop"
+	echo "          nm-quick.sh -e -b version -t $LATEST -a -d example.com"
 	exit 1
 }
 
-while getopts evab:t: flag; do
+while getopts evab:d:t: flag; do
 	case "${flag}" in
 	e)
 		INSTALL_TYPE="ee"
@@ -60,6 +63,9 @@ while getopts evab:t: flag; do
 	t)
 		BUILD_TAG=${OPTARG}
 		;;
+	d)
+		NETMAKER_BASE_DOMAIN=${OPTARG}
+		;;
 	esac
 done
 
@@ -303,9 +309,9 @@ save_config() { (
 	local toCopy=("SERVER_HOST" "MASTER_KEY" "TURN_USERNAME" "TURN_PASSWORD" "MQ_USERNAME" "MQ_PASSWORD"
 		"INSTALL_TYPE" "NODE_ID" "METRICS_EXPORTER" "PROMETHEUS" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
-		"DEFAULT_PROXY_MODE" "TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK"
-		"TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET" "FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER"
-		"EXPORTER_API_PORT")
+		"DEFAULT_PROXY_MODE" "TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
+		"DISABLE_REMOTE_IP_CHECK" "NETCLIENT_ENDPOINT_DETECTION" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT")
 	for name in "${toCopy[@]}"; do
 		save_config_item $name "${!name}"
 	done
@@ -490,8 +496,9 @@ set_install_vars() {
 	if [ "$IP_ADDR" = "" ]; then
 		IP_ADDR=$(curl -s ifconfig.me)
 	fi
-
-	NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then
+		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	fi
 	SERVER_HOST=$IP_ADDR
 	if test -z "$MASTER_KEY"; then
 		MASTER_KEY=$(

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

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-LATEST="v0.20.1"
+LATEST="v0.20.3"
 INSTALL_PATH="/root"
 
 trap restore_old_netmaker_instructions

+ 16 - 0
servercfg/serverconf.go

@@ -52,6 +52,11 @@ func GetServerConfig() config.ServerConfig {
 	} else {
 		cfg.NetclientAutoUpdate = "disabled"
 	}
+	if EndpointDetectionEnabled() {
+		cfg.NetclientEndpointDetection = "enabled"
+	} else {
+		cfg.NetclientEndpointDetection = "disabled"
+	}
 	if IsRestBackend() {
 		cfg.RestBackend = "on"
 	}
@@ -433,6 +438,17 @@ func AutoUpdateEnabled() bool {
 	return true
 }
 
+// EndpointDetectionEnabled returns a boolean indicating whether netclient endpoint detection is enabled or disabled
+// default is enabled
+func EndpointDetectionEnabled() bool {
+	if os.Getenv("NETCLIENT_ENDPOINT_DETECTION") == "disabled" {
+		return false
+	} else if config.Config.Server.NetclientEndpointDetection == "disabled" {
+		return false
+	}
+	return true
+}
+
 // IsDNSMode - should it run with DNS
 func IsDNSMode() bool {
 	isdns := true

+ 1 - 1
swagger.yaml

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