Browse Source

merge conflicts resolved

Abhishek Kondur 2 years ago
parent
commit
b29f2f09c9

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

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

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

@@ -21,7 +21,7 @@ jobs:
     needs: skip-check
     needs: skip-check
     if: ${{ needs.skip-check.outputs.skip != 'true' }}
     if: ${{ needs.skip-check.outputs.skip != 'true' }}
     outputs:
     outputs:
-      netclientbranch: ${{ steps.checkbranch.outputs.netclientbranch }}
+      netclientbranch: ${{ steps.getbranch.outputs.netclientbranch }}
     steps:
     steps:
       - name: checkout
       - name: checkout
         uses: actions/checkout@v3
         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 GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.18.0
+FROM alpine:3.18.2
 
 
 # add a c lib
 # add a c lib
 # set the working directory
 # set the working directory

+ 1 - 1
Dockerfile-quick

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

+ 3 - 4
README.md

@@ -6,18 +6,17 @@
 </p>
 </p>
 
 
 <p align="center">
 <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>  
 <a href="https://www.ycombinator.com/companies/netmaker/" target="_blank" rel="noopener">
 <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%" />
     <img src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/y-combinator.png" alt="Y-Combinator" width="16%" />
 </a>  
 </a>  
-
 </p>
 </p>
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.20.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.20.3-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 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 (
 import (
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/cli/functions"
@@ -6,12 +6,12 @@ import (
 )
 )
 
 
 var hostDeleteRelayCmd = &cobra.Command{
 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) {
 	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
 	postUp                 string
 	postDown               string
 	postDown               string
 	keepAlive              int
 	keepAlive              int
-	relayAddrs             string
+	relayedNodes           string
 	egressGatewayRanges    string
 	egressGatewayRanges    string
 	expirationDateTime     int
 	expirationDateTime     int
 	defaultACL             bool
 	defaultACL             bool

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

@@ -35,8 +35,8 @@ var nodeUpdateCmd = &cobra.Command{
 			node.Address6 = address6
 			node.Address6 = address6
 			node.LocalAddress = localAddress
 			node.LocalAddress = localAddress
 			node.PersistentKeepalive = int32(keepAlive)
 			node.PersistentKeepalive = int32(keepAlive)
-			if relayAddrs != "" {
-				node.RelayAddrs = strings.Split(relayAddrs, ",")
+			if relayedNodes != "" {
+				node.RelayedNodes = strings.Split(relayedNodes, ",")
 			}
 			}
 			if egressGatewayRanges != "" {
 			if egressGatewayRanges != "" {
 				node.EgressGatewayRanges = strings.Split(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(&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().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().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().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().IntVar(&expirationDateTime, "expiry", 0, "UNIX timestamp after which node will lose access to the network")
 	nodeUpdateCmd.Flags().BoolVar(&defaultACL, "acl", false, "Enable default ACL ?")
 	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)
 	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
 // RefreshKeys - refresh wireguard keys

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

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

+ 2 - 1
compose/docker-compose.yml

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

+ 56 - 55
config/config.go

@@ -32,61 +32,62 @@ type EnvironmentConfig struct {
 
 
 // ServerConfig - server conf struct
 // ServerConfig - server conf struct
 type ServerConfig 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
 // ProxyMode - default proxy mode for server

+ 1 - 1
controllers/docs.go

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

+ 4 - 42
controllers/hosts.go

@@ -6,7 +6,6 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
-	"reflect"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
 	"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}", 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(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}/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/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)
 	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"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+
 	serverConf.TrafficKey = key
 	serverConf.TrafficKey = key
 	response := models.HostPull{
 	response := models.HostPull{
 		Host:         *host,
 		Host:         *host,
+		Nodes:        logic.GetHostNodes(host),
 		ServerConfig: serverConf,
 		ServerConfig: serverConf,
 		Peers:        hPU.Peers,
 		Peers:        hPU.Peers,
 		PeerIDs:      hPU.PeerIDs,
 		PeerIDs:      hPU.PeerIDs,
@@ -172,13 +171,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	newHost := newHostData.ConvertAPIHostToNMHost(currHost)
 	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
 	logic.UpdateHost(newHost, currHost) // update the in memory struct values
 	if err = logic.UpsertHost(newHost); err != nil {
 	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"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	if updateRelay {
-		logic.UpdateHostRelay(currHost.ID.String(), currHost.RelayedHosts, newHost.RelayedHosts)
-	}
 	// publish host update through MQ
 	// publish host update through MQ
 	if err := mq.HostUpdate(&models.HostUpdate{
 	if err := mq.HostUpdate(&models.HostUpdate{
 		Action: models.UpdateHost,
 		Action: models.UpdateHost,
@@ -242,33 +231,6 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	if 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 {
 	if err = logic.RemoveHost(currHost); err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error())
 		logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))

+ 48 - 0
controllers/network.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
+	"golang.org/x/exp/slog"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"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", 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(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(deleteNetwork))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	// ACLs
 	// 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(updateNetworkACL))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
 	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)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(network)
 	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) {
 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}/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}/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/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 }
@@ -154,7 +152,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 // even if it's technically ok
 // even if it's technically ok
 // This is kind of a poor man's RBAC. There's probably a better/smarter way.
 // This is kind of a poor man's RBAC. There's probably a better/smarter way.
 // TODO: Consider better RBAC implementations
 // 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) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
 		var errorResponse = models.ErrorResponse{
 			Code: http.StatusForbidden, Message: logic.Forbidden_Msg,
 			Code: http.StatusForbidden, Message: logic.Forbidden_Msg,
@@ -633,12 +631,12 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	newNode := newData.ConvertToServerNode(&currentNode)
 	newNode := newData.ConvertToServerNode(&currentNode)
 	relayupdate := false
 	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
 			relayupdate = true
 		} else {
 		} 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
 					relayupdate = true
 				}
 				}
 			}
 			}
@@ -651,10 +649,6 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		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)
 	ifaceDelta := logic.IfaceDelta(&currentNode, newNode)
 	aclUpdate := currentNode.DefaultACL != newNode.DefaultACL
 	aclUpdate := currentNode.DefaultACL != newNode.DefaultACL
 	if ifaceDelta && servercfg.Is_EE {
 	if ifaceDelta && servercfg.Is_EE {
@@ -671,16 +665,13 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	if relayupdate {
 	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 {
 		if len(updatenodes) > 0 {
 			for _, relayedNode := range updatenodes {
 			for _, relayedNode := range updatenodes {
 				runUpdates(&relayedNode, false)
 				runUpdates(&relayedNode, false)
 			}
 			}
 		}
 		}
 	}
 	}
-	if relayedUpdate {
-		updateRelay(&currentNode, newNode)
-	}
 	if servercfg.IsDNSMode() {
 	if servercfg.IsDNSMode() {
 		logic.SetDNS()
 		logic.SetDNS()
 	}
 	}
@@ -690,8 +681,8 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
 	json.NewEncoder(w).Encode(apiNode)
 	runUpdates(newNode, ifaceDelta)
 	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 {
 			if err := mq.PublishPeerUpdate(); err != nil {
 				logger.Log(0, "error during node ACL update for node", newNode.ID.String())
 				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 {
 		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
 			logger.Log(1, "failed to publish dns update", err.Error())
 			logger.Log(1, "failed to publish dns update", err.Error())
 		}
 		}
-	}(aclUpdate, newNode)
+	}(aclUpdate, relayupdate, newNode)
 }
 }
 
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
 // 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"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 		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.")
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	if !fromNode { // notify node change
 	if !fromNode { // notify node change
@@ -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 {
 func doesUserOwnNode(username, network, nodeID string) bool {
 	u, err := logic.GetUser(username)
 	u, err := logic.GetUser(username)
 	if err != nil {
 	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!!"))
 		resp.Write([]byte("Server is up and running!!"))
 	}))
 	}))
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/getserverinfo", authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
-	r.HandleFunc("/api/server/usage", authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
 }
 }
 func getUsage(w http.ResponseWriter, r *http.Request) {
 func getUsage(w http.ResponseWriter, r *http.Request) {
 	type usage struct {
 	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.MetricHandlers,
 		ee_controllers.NetworkUsersHandlers,
 		ee_controllers.NetworkUsersHandlers,
 		ee_controllers.UserGroupsHandlers,
 		ee_controllers.UserGroupsHandlers,
+		ee_controllers.RelayHandlers,
 	)
 	)
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==
 		// == License Handling ==

+ 9 - 9
go.mod

@@ -4,22 +4,22 @@ go 1.19
 
 
 require (
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
 	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/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
 	github.com/lib/pq v1.10.9
 	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/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.8.3
+	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.4.0
 	github.com/txn2/txeh v1.4.0
-	golang.org/x/crypto v0.9.0
-	golang.org/x/net v0.10.0 // indirect
-	golang.org/x/oauth2 v0.8.0
-	golang.org/x/sys v0.8.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/crypto v0.10.0
+	golang.org/x/net v0.11.0 // indirect
+	golang.org/x/oauth2 v0.9.0
+	golang.org/x/sys v0.9.0 // indirect
+	golang.org/x/text v0.10.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -36,7 +36,7 @@ require (
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/pkg/errors v0.9.1
 	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
 	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/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.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 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 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=
 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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 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-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 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
 github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
 github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
 github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
 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.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.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.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 h1:0tdvpA4HGJrj8X3kmrU6o/JFStI009nKxwDpMK5CnRU=
 github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc=
 github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
-golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
-golang.org/x/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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 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-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
-golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
+golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
+golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
+golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

+ 6 - 0
logic/gateway.go

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

+ 1 - 0
logic/hosts.go

@@ -118,6 +118,7 @@ func CreateHost(h *models.Host) error {
 	}
 	}
 	h.HostPass = string(hash)
 	h.HostPass = string(hash)
 	h.AutoUpdate = servercfg.AutoUpdateEnabled()
 	h.AutoUpdate = servercfg.AutoUpdateEnabled()
+	h.EndpointDetection = servercfg.EndpointDetectionEnabled()
 	// if another server has already updated proxyenabled, leave it alone
 	// if another server has already updated proxyenabled, leave it alone
 	if !h.ProxyEnabledSet {
 	if !h.ProxyEnabledSet {
 		log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)
 		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
 	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
 // GetNetworkNodesMemory - gets all nodes belonging to a network from list in memory
 func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
 func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
 	var nodes = []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)
 	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
 // UpdateNode - takes a node and updates another node with it's values
 func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 	if newNode.Address.IP.String() != currentNode.Address.IP.String() {
 	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
 	nodeACLDelta := currentNode.DefaultACL != newNode.DefaultACL
-	newNode.Fill(currentNode)
+	newNode.Fill(currentNode, servercfg.Is_EE)
 
 
 	// check for un-settable server values
 	// check for un-settable server values
 	if err := ValidateNode(newNode, true); err != nil {
 	if err := ValidateNode(newNode, true); err != nil {
@@ -326,34 +348,6 @@ func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node,
 	return node, nil
 	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) {
 func GetNodeByID(uuid string) (models.Node, error) {
 	var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
 	var record, err = database.FetchRecord(database.NODES_TABLE_NAME, uuid)
 	if err != nil {
 	if err != nil {
@@ -387,24 +381,12 @@ func GetDeletedNodeByID(uuid string) (models.Node, error) {
 
 
 // FindRelay - returns the node that is the relay for a relayed node
 // FindRelay - returns the node that is the relay for a relayed node
 func FindRelay(node *models.Node) *models.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 {
 	if err != nil {
+		logger.Log(0, "FindRelay: "+err.Error())
 		return nil
 		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
 // 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/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slices"
 	"golang.org/x/exp/slices"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
@@ -29,41 +30,6 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 		Action: models.ProxyUpdate,
 		Action: models.ProxyUpdate,
 	}
 	}
 	peerConfMap := make(map[string]models.PeerConf)
 	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
 	var ingressStatus bool
 	for _, nodeID := range host.Nodes {
 	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
 			peerConfMap[peerHost.PublicKey.String()] = currPeerConf
 		}
 		}
 		if node.IsIngressGateway {
 		if node.IsIngressGateway {
@@ -167,7 +121,10 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 		HostNetworkInfo: models.HostInfoMap{},
 		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)
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
 	for _, nodeID := range host.Nodes {
 		nodeID := nodeID
 		nodeID := nodeID
@@ -195,58 +152,17 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					//skip yourself
 					//skip yourself
 					continue
 					continue
 				}
 				}
-				var peerConfig wgtypes.PeerConfig
+
 				peerHost, err := GetHost(peer.HostID.String())
 				peerHost, err := GetHost(peer.HostID.String())
 				if err != nil {
 				if err != nil {
 					logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
 					logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
 					return models.HostPeerUpdate{}, err
 					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 node.IsIngressGateway || node.IsEgressGateway {
 					if peer.IsIngressGateway {
 					if peer.IsIngressGateway {
 						_, extPeerIDAndAddrs, err := getExtPeers(&peer)
 						_, extPeerIDAndAddrs, err := getExtPeers(&peer)
@@ -279,6 +195,47 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 						ID:      peer.ID.String(),
 						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)
 				peerProxyPort := GetProxyListenPort(peerHost)
 				var nodePeer wgtypes.PeerConfig
 				var nodePeer wgtypes.PeerConfig
@@ -300,8 +257,9 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					nodePeer = peerConfig
 					nodePeer = peerConfig
 				} else {
 				} else {
 					peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
 					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()]].AllowedIPs = peerAllowedIPs
+					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].Remove = false
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 						ID:              peer.ID.String(),
 						ID:              peer.ID.String(),
 						Address:         peer.PrimaryAddress(),
 						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
 	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())
 	peerHost, err := GetHost(peer.HostID.String())
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "error retrieving host for peer", peer.ID.String(), err.Error())
 		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
 		// getting the public ip of node
 		if ipnet.Contains(peerHost.EndpointIP) && !internetGateway { // ensuring egress gateway range does not contain endpoint 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
 			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
 		// 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
 			continue // skip adding egress range if overlaps with node's local ip
 		}
 		}
 		if err != nil {
 		if err != nil {
@@ -687,12 +646,50 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet {
 	// handle egress gateway peers
 	// handle egress gateway peers
 	if peer.IsEgressGateway {
 	if peer.IsEgressGateway {
 		//hasGateway = true
 		//hasGateway = true
-		egressIPs := getEgressIPs(node, peer)
+		egressIPs := getEgressIPs(peer)
 		allowedips = append(allowedips, egressIPs...)
 		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
 	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 {
 func getCIDRMaskFromAddr(addr string) net.IPMask {
 	cidr := net.CIDRMask(32, 32)
 	cidr := net.CIDRMask(32, 32)
 	ipAddr, err := netip.ParseAddr(addr)
 	ipAddr, err := netip.ParseAddr(addr)

+ 89 - 145
logic/relay.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"time"
+	"net"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
@@ -31,8 +31,7 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
 		return returnnodes, models.Node{}, err
 		return returnnodes, models.Node{}, err
 	}
 	}
 	node.IsRelay = true
 	node.IsRelay = true
-	node.RelayAddrs = relay.RelayAddrs
-
+	node.RelayedNodes = relay.RelayedNodes
 	node.SetLastModified()
 	node.SetLastModified()
 	nodeData, err := json.Marshal(&node)
 	nodeData, err := json.Marshal(&node)
 	if err != nil {
 	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 {
 	if err = database.Insert(node.ID.String(), string(nodeData), database.NODES_TABLE_NAME); err != nil {
 		return returnnodes, models.Node{}, err
 		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
 	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
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
 	var err error
 	//isIp := functions.IsIpCIDR(gateway.RangeString)
 	//isIp := functions.IsIpCIDR(gateway.RangeString)
-	empty := len(relay.RelayAddrs) == 0
+	empty := len(relay.RelayedNodes) == 0
 	if empty {
 	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 {
 	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
 // DeleteRelay - deletes a relay
@@ -188,15 +141,10 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	if err != nil {
 	if err != nil {
 		return returnnodes, models.Node{}, err
 		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.IsRelay = false
-	node.RelayAddrs = []string{}
+	node.RelayedNodes = []string{}
 	node.SetLastModified()
 	node.SetLastModified()
-
 	data, err := json.Marshal(&node)
 	data, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {
 		return returnnodes, models.Node{}, err
 		return returnnodes, models.Node{}, err
@@ -207,24 +155,20 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	return returnnodes, node, nil
 	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 {
 	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 newNode.IsRelay {
-		if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) {
+		if len(currentNode.RelayedNodes) != len(newNode.RelayedNodes) {
 			return true
 			return true
 		}
 		}
-		for _, address := range newNode.RelayAddrs {
-			if !StringSliceContains(currentNode.RelayAddrs, address) {
+		for _, node := range newNode.RelayedNodes {
+			if !StringSliceContains(currentNode.RelayedNodes, node) {
 				return true
 				return true
 			}
 			}
 		}
 		}

+ 22 - 1
main.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
+	"path/filepath"
 	"runtime/debug"
 	"runtime/debug"
 	"sync"
 	"sync"
 	"syscall"
 	"syscall"
@@ -26,9 +27,10 @@ import (
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	"github.com/gravitl/netmaker/serverctl"
 	stunserver "github.com/gravitl/netmaker/stun-server"
 	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
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {
@@ -184,6 +186,25 @@ func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
 func setVerbosity() {
 func setVerbosity() {
 	verbose := int(servercfg.GetVerbosity())
 	verbose := int(servercfg.GetVerbosity())
 	logger.Verbosity = verbose
 	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() {
 func setGarbageCollection() {

+ 0 - 8
models/api_host.go

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

+ 7 - 3
models/api_node.go

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

+ 1 - 0
models/host.go

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

+ 9 - 8
models/node.go

@@ -69,6 +69,10 @@ type CommonNode struct {
 	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
 	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
 	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"`
 	IngressDNS          string        `json:"ingressdns" yaml:"ingressdns"`
 	DNSOn               bool          `json:"dnson" yaml:"dnson"`
 	DNSOn               bool          `json:"dnson" yaml:"dnson"`
 	PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
 	PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
@@ -86,9 +90,6 @@ type Node struct {
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	IngressGatewayRange6    string               `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
 	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 ==
 	// == PRO ==
 	DefaultACL   string    `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	DefaultACL   string    `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
 	OwnerID      string    `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
 	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
 // 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.ID = currentNode.ID
 	newNode.HostID = currentNode.HostID
 	newNode.HostID = currentNode.HostID
 	// Revisit the logic for boolean values
 	// 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 == "" {
 	if newNode.Action == "" {
 		newNode.Action = currentNode.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
 		newNode.IsRelay = currentNode.IsRelay
 	}
 	}
-	if newNode.IsRelayed == currentNode.IsRelayed {
+	if newNode.IsRelayed == currentNode.IsRelayed && isEE {
 		newNode.IsRelayed = currentNode.IsRelayed
 		newNode.IsRelayed = currentNode.IsRelayed
 	}
 	}
 	if newNode.Server == "" {
 	if newNode.Server == "" {

+ 4 - 3
models/structs.go

@@ -154,9 +154,9 @@ type EgressGatewayRequest struct {
 
 
 // RelayRequest - relay request struct
 // RelayRequest - relay request struct
 type RelayRequest 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
 // HostRelayRequest - struct for host relay creation
@@ -202,6 +202,7 @@ type TrafficKeys struct {
 // HostPull - response of a host's pull
 // HostPull - response of a host's pull
 type HostPull struct {
 type HostPull struct {
 	Host         Host                 `json:"host" yaml:"host"`
 	Host         Host                 `json:"host" yaml:"host"`
+	Nodes        []Node               `json:"nodes" yaml:"nodes"`
 	Peers        []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
 	Peers        []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
 	ServerConfig ServerConfig         `json:"server_config" yaml:"server_config"`
 	ServerConfig ServerConfig         `json:"server_config" yaml:"server_config"`
 	PeerIDs      PeerMap              `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"`
 	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"
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
 // DefaultHandler default message queue handler  -- NOT USED
 // DefaultHandler default message queue handler  -- NOT USED
 func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
 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
 // UpdateNode  message Handler -- handles updates from client nodes
 func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	id, err := getID(msg.Topic())
 	if err != nil {
 	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
 		return
 	}
 	}
 	currentNode, err := logic.GetNodeByID(id)
 	currentNode, err := logic.GetNodeByID(id)
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "error getting node ", id, err.Error())
+		slog.Error("error getting node", "id", id, "error", err)
 		return
 		return
 	}
 	}
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	if decryptErr != nil {
 	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
 		return
 	}
 	}
 	var newNode models.Node
 	var newNode models.Node
 	if err := json.Unmarshal(decrypted, &newNode); err != nil {
 	if err := json.Unmarshal(decrypted, &newNode); err != nil {
-		logger.Log(1, "error unmarshaling payload ", err.Error())
+		slog.Error("error unmarshaling payload", "error", err)
 		return
 		return
 	}
 	}
 
 
 	ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
 	ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
 	if servercfg.Is_EE && ifaceDelta {
 	if servercfg.Is_EE && ifaceDelta {
 		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
 		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()
 	newNode.SetLastCheckIn()
 	if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
 	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
 		return
 	}
 	}
 	if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
 	if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
 		if err = PublishPeerUpdate(); err != nil {
 		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
 // UpdateHost  message Handler -- handles host updates from clients
 func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	id, err := getID(msg.Topic())
 	if err != nil {
 	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
 		return
 	}
 	}
 	currentHost, err := logic.GetHost(id)
 	currentHost, err := logic.GetHost(id)
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "error getting host ", id, err.Error())
+		slog.Error("error getting host", "id", id, "error", err)
 		return
 		return
 	}
 	}
 	decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
 	decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
 	if decryptErr != nil {
 	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
 		return
 	}
 	}
 	var hostUpdate models.HostUpdate
 	var hostUpdate models.HostUpdate
 	if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
 	if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
-		logger.Log(1, "error unmarshaling payload ", err.Error())
+		slog.Error("error unmarshaling payload", "error", err)
 		return
 		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
 	var sendPeerUpdate bool
 	switch hostUpdate.Action {
 	switch hostUpdate.Action {
 	case models.CheckIn:
 	case models.CheckIn:
@@ -98,21 +98,21 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		hu := hostactions.GetAction(currentHost.ID.String())
 		hu := hostactions.GetAction(currentHost.ID.String())
 		if hu != nil {
 		if hu != nil {
 			if err = HostUpdate(hu); err != 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
 				return
 			} else {
 			} else {
 				if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 				if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 					if err = AppendNodeUpdateACL(hu.Host.ID.String(), hu.Node.Network, hu.Node.ID.String(), servercfg.GetServer()); err != nil {
 					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
 						return
 					}
 					}
 				}
 				}
 				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil, nil); err != nil {
 				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
 					return
 				}
 				}
 				if err = handleNewNodeDNS(&hu.Host, &hu.Node); err != nil {
 				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
 					return
 				}
 				}
 			}
 			}
@@ -131,7 +131,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			}
 			}
 			data, err := json.Marshal(&peerUpdate)
 			data, err := json.Marshal(&peerUpdate)
 			if err != nil {
 			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())
 			hosts := logic.GetRelatedHosts(hostUpdate.Host.ID.String())
 			server := servercfg.GetServer()
 			server := servercfg.GetServer()
@@ -143,23 +143,23 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 		err := logic.UpsertHost(currentHost)
 		err := logic.UpsertHost(currentHost)
 		if err != nil {
 		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
 			return
 		}
 		}
 	case models.DeleteHost:
 	case models.DeleteHost:
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 			// delete EMQX credentials for host
 			// delete EMQX credentials for host
 			if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
 			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
 				return
 			}
 			}
 		}
 		}
 		if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
 		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
 			return
 		}
 		}
 		if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
 		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
 			return
 		}
 		}
 		sendPeerUpdate = true
 		sendPeerUpdate = true
@@ -167,7 +167,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		if servercfg.IsUsingTurn() {
 		if servercfg.IsUsingTurn() {
 			err = logic.RegisterHostWithTurn(hostUpdate.Host.ID.String(), hostUpdate.Host.HostPass)
 			err = logic.RegisterHostWithTurn(hostUpdate.Host.ID.String(), hostUpdate.Host.HostPass)
 			if err != nil {
 			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
 				return
 			}
 			}
 		}
 		}
@@ -177,7 +177,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 	if sendPeerUpdate {
 	if sendPeerUpdate {
 		err := PublishPeerUpdate()
 		err := PublishPeerUpdate()
 		if err != nil {
 		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 {
 	// if servercfg.Is_EE && ifaceDelta {
@@ -192,57 +192,55 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 	if servercfg.Is_EE {
 	if servercfg.Is_EE {
 		id, err := getID(msg.Topic())
 		id, err := getID(msg.Topic())
 		if err != nil {
 		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
 			return
 		}
 		}
 		currentNode, err := logic.GetNodeByID(id)
 		currentNode, err := logic.GetNodeByID(id)
 		if err != nil {
 		if err != nil {
-			logger.Log(1, "error getting node ", id, err.Error())
+			slog.Error("error getting node", "id", id, "error", err)
 			return
 			return
 		}
 		}
 		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 		if decryptErr != nil {
 		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
 			return
 		}
 		}
 
 
 		var newMetrics models.Metrics
 		var newMetrics models.Metrics
 		if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
 		if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
-			logger.Log(1, "error unmarshaling payload ", err.Error())
+			slog.Error("error unmarshaling payload", "error", err)
 			return
 			return
 		}
 		}
 
 
 		shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
 		shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
 
 
 		if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
 		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
 			return
 		}
 		}
 		if servercfg.IsMetricsExporter() {
 		if servercfg.IsMetricsExporter() {
 			if err := pushMetricsToExporter(newMetrics); err != nil {
 			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 {
 		if newMetrics.Connectivity != nil {
 			err := logic.EnterpriseFailoverFunc(&currentNode)
 			err := logic.EnterpriseFailoverFunc(&currentNode)
 			if err != nil {
 			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 {
 		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())
 			host, err := logic.GetHost(currentNode.HostID.String())
 			if err == nil {
 			if err == nil {
 				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil, nil); 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) {
 func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 	id, err := getID(msg.Topic())
 	id, err := getID(msg.Topic())
 	if err != nil {
 	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
 		return
 	}
 	}
 	currentNode, err := logic.GetNodeByID(id)
 	currentNode, err := logic.GetNodeByID(id)
 	if err != nil {
 	if err != nil {
-		logger.Log(1, "error getting node ", id, err.Error())
+		slog.Error("error getting node", "id", id, "error", err)
 		return
 		return
 	}
 	}
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 	if decryptErr != nil {
 	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
 		return
 	}
 	}
 	switch decrypted[0] {
 	switch decrypted[0] {
@@ -268,12 +266,12 @@ func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 		// do we still need this
 		// do we still need this
 	case ncutils.DONE:
 	case ncutils.DONE:
 		if err = PublishPeerUpdate(); err != nil {
 		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
 			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 {
 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())
 	oldMetrics, err := logic.GetMetrics(currentNode.ID.String())
 	if err != nil {
 	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
 		return false
 	}
 	}
 	if oldMetrics.FailoverPeers == nil {
 	if oldMetrics.FailoverPeers == nil {
@@ -349,7 +347,7 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 	// add nodes that need failover
 	// add nodes that need failover
 	nodes, err := logic.GetNetworkNodes(currentNode.Network)
 	nodes, err := logic.GetNetworkNodes(currentNode.Network)
 	if err != nil {
 	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
 		return false
 	}
 	}
 	for _, node := range nodes {
 	for _, node := range nodes {
@@ -415,13 +413,13 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
 				fakeNode.Action = models.NODE_DELETE
 				fakeNode.Action = models.NODE_DELETE
 				fakeNode.PendingDelete = true
 				fakeNode.PendingDelete = true
 				if err := NodeUpdate(&fakeNode); err != nil {
 				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
 			continue
 		}
 		}
 		if err := logic.UpdateNodeCheckin(&node); err != nil {
 		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.FirewallInUse = h.FirewallInUse
 		currentHost.Version = h.Version
 		currentHost.Version = h.Version
 		if err := logic.UpsertHost(currentHost); err != nil {
 		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
 			return false
 		}
 		}
 	}
 	}
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||
-		h.DefaultInterface != currentHost.DefaultInterface
+		h.DefaultInterface != currentHost.DefaultInterface ||
+		h.EndpointDetection != servercfg.EndpointDetectionEnabled()
 	if ifaceDelta { // only save if something changes
 	if ifaceDelta { // only save if something changes
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.Interfaces = h.Interfaces
 		currentHost.Interfaces = h.Interfaces
 		currentHost.DefaultInterface = h.DefaultInterface
 		currentHost.DefaultInterface = h.DefaultInterface
 		currentHost.NatType = h.NatType
 		currentHost.NatType = h.NatType
 		if err := logic.UpsertHost(currentHost); err != nil {
 		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
 			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
 	return ifaceDelta
 }
 }

+ 1 - 1
release.md

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

+ 1 - 0
scripts/netmaker.default.env

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

+ 15 - 8
scripts/nm-quick.sh

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

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

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

+ 16 - 0
servercfg/serverconf.go

@@ -52,6 +52,11 @@ func GetServerConfig() config.ServerConfig {
 	} else {
 	} else {
 		cfg.NetclientAutoUpdate = "disabled"
 		cfg.NetclientAutoUpdate = "disabled"
 	}
 	}
+	if EndpointDetectionEnabled() {
+		cfg.NetclientEndpointDetection = "enabled"
+	} else {
+		cfg.NetclientEndpointDetection = "disabled"
+	}
 	if IsRestBackend() {
 	if IsRestBackend() {
 		cfg.RestBackend = "on"
 		cfg.RestBackend = "on"
 	}
 	}
@@ -433,6 +438,17 @@ func AutoUpdateEnabled() bool {
 	return true
 	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
 // IsDNSMode - should it run with DNS
 func IsDNSMode() bool {
 func IsDNSMode() bool {
 	isdns := true
 	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.
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
     title: Netmaker
-    version: 0.20.1
+    version: 0.20.3
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: