From a899544104d4ec2821c115fa6f38f96397605071 Mon Sep 17 00:00:00 2001 From: Aceix Date: Wed, 22 Jan 2025 09:30:13 +0000 Subject: [PATCH 01/12] refactor: allow friendlier tag names (#3304) - update tag name validation to allow spaces and numbers --- logic/tags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/tags.go b/logic/tags.go index 0c2aa095..e1f9de5f 100644 --- a/logic/tags.go +++ b/logic/tags.go @@ -259,7 +259,7 @@ func CheckIDSyntax(id string) error { if len(id) < 3 { return errors.New("name should have min 3 characters") } - reg, err := regexp.Compile("^[a-zA-Z-]+$") + reg, err := regexp.Compile("^[a-zA-Z0-9- ]+$") if err != nil { return err } From c02ec193db70f9c71d0b78b51529139bcf43dc56 Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 28 Jan 2025 11:26:12 +0530 Subject: [PATCH 02/12] NET-1920: Add disconnected node status (#3300) * create peer ack table * add restricted status * add disconnected status --- database/database.go | 3 +++ logic/status.go | 4 ++++ models/node.go | 11 ++++++----- pro/logic/status.go | 23 +++++++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/database/database.go b/database/database.go index 6590e006..c17465ba 100644 --- a/database/database.go +++ b/database/database.go @@ -71,6 +71,8 @@ const ( USER_INVITES_TABLE_NAME = "user_invites" // TAG_TABLE_NAME - table for tags TAG_TABLE_NAME = "tags" + // PEER_ACK_TABLE - table for failover peer ack + PEER_ACK_TABLE = "peer_ack" // == ERROR CONSTS == // NO_RECORD - no singular result found NO_RECORD = "no result found" @@ -158,6 +160,7 @@ func createTables() { CreateTable(USER_INVITES_TABLE_NAME) CreateTable(TAG_TABLE_NAME) CreateTable(ACLS_TABLE_NAME) + CreateTable(PEER_ACK_TABLE) } func CreateTable(tableName string) error { diff --git a/logic/status.go b/logic/status.go index e0511ae0..15d98a31 100644 --- a/logic/status.go +++ b/logic/status.go @@ -18,6 +18,10 @@ func getNodeStatus(node *models.Node, t bool) { node.Status = models.OnlineSt return } + if !node.Connected { + node.Status = models.Disconnected + return + } if time.Since(node.LastCheckIn) > time.Minute*10 { node.Status = models.OfflineSt return diff --git a/models/node.go b/models/node.go index c9e55e5d..b97b35c2 100644 --- a/models/node.go +++ b/models/node.go @@ -14,11 +14,12 @@ import ( type NodeStatus string const ( - OnlineSt NodeStatus = "online" - OfflineSt NodeStatus = "offline" - WarningSt NodeStatus = "warning" - ErrorSt NodeStatus = "error" - UnKnown NodeStatus = "unknown" + OnlineSt NodeStatus = "online" + OfflineSt NodeStatus = "offline" + WarningSt NodeStatus = "warning" + ErrorSt NodeStatus = "error" + UnKnown NodeStatus = "unknown" + Disconnected NodeStatus = "disconnected" ) // LastCheckInThreshold - if node's checkin more than this threshold,then node is declared as offline diff --git a/pro/logic/status.go b/pro/logic/status.go index 82e2c4ea..a23ff21d 100644 --- a/pro/logic/status.go +++ b/pro/logic/status.go @@ -17,6 +17,10 @@ func getNodeStatusOld(node *models.Node) { node.Status = models.OnlineSt return } + if !node.Connected { + node.Status = models.Disconnected + return + } if time.Since(node.LastCheckIn) > time.Minute*10 { node.Status = models.OfflineSt return @@ -31,12 +35,25 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) { node.Status = models.OfflineSt return } + ingNode, err := logic.GetNodeByID(node.StaticNode.IngressGatewayID) + if err != nil { + node.Status = models.OfflineSt + return + } + if !defaultEnabledPolicy { + allowed, _ := logic.IsNodeAllowedToCommunicate(*node, ingNode, false) + if !allowed { + node.Status = models.OnlineSt + return + } + } // check extclient connection from metrics ingressMetrics, err := GetMetrics(node.StaticNode.IngressGatewayID) if err != nil || ingressMetrics == nil || ingressMetrics.Connectivity == nil { node.Status = models.UnKnown return } + if metric, ok := ingressMetrics.Connectivity[node.StaticNode.ClientID]; ok { if metric.Connected { node.Status = models.OnlineSt @@ -46,9 +63,14 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) { return } } + node.Status = models.UnKnown return } + if !node.Connected { + node.Status = models.Disconnected + return + } if time.Since(node.LastCheckIn) > models.LastCheckInThreshold { node.Status = models.OfflineSt return @@ -197,6 +219,7 @@ func checkPeerConnectivity(node *models.Node, metrics *models.Metrics, defaultAc peerNotConnectedCnt++ } + if peerNotConnectedCnt > len(metrics.Connectivity)/2 { node.Status = models.WarningSt return From 6509da9b617fab3f44fcd234ffbc1d9030af7b87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:27:09 +0530 Subject: [PATCH 03/12] Bump dawidd6/action-download-artifact from 7 to 8 (#3310) Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 7 to 8. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deletedroplets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index f3412e2e..6bd93b66 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -12,7 +12,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: get logs - uses: dawidd6/action-download-artifact@v7 + uses: dawidd6/action-download-artifact@v8 with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn @@ -75,7 +75,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'failure' }} steps: - name: get logs - uses: dawidd6/action-download-artifact@v7 + uses: dawidd6/action-download-artifact@v8 with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn From 8297642b90fe26545184287b1d5175589f58e8ce Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 28 Jan 2025 11:28:31 +0530 Subject: [PATCH 04/12] NET-1914: add gw apis, move relays into CE (#3309) * add gw apis, move relays into CE * set gw field on relay and ingress creation * add gw handlers to relay and ingress apis * if node is inetgw and gw add dns * remove pro check on relays * fetch node before updating --- controllers/controller.go | 1 + controllers/gateway.go | 207 +++++++++++++++++++++++++++++ controllers/node.go | 111 +--------------- logic/gateway.go | 8 +- logic/peers.go | 24 ++-- logic/pro/failover | 0 logic/relay.go | 238 +++++++++++++++++++++++++++++++-- migrate/migrate.go | 16 +++ models/gateway.go | 9 ++ models/node.go | 9 +- pro/controllers/inet_gws.go | 3 + pro/controllers/relay.go | 152 --------------------- pro/initialize.go | 8 -- pro/logic/relays.go | 255 ------------------------------------ 14 files changed, 485 insertions(+), 556 deletions(-) create mode 100644 controllers/gateway.go create mode 100644 logic/pro/failover create mode 100644 models/gateway.go delete mode 100644 pro/controllers/relay.go delete mode 100644 pro/logic/relays.go diff --git a/controllers/controller.go b/controllers/controller.go index 317536dd..93e8bba3 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -24,6 +24,7 @@ var HttpMiddlewares = []mux.MiddlewareFunc{ // HttpHandlers - handler functions for REST interactions var HttpHandlers = []interface{}{ nodeHandlers, + gwHandlers, userHandlers, networkHandlers, dnsHandlers, diff --git a/controllers/gateway.go b/controllers/gateway.go new file mode 100644 index 00000000..4a25f33e --- /dev/null +++ b/controllers/gateway.go @@ -0,0 +1,207 @@ +package controller + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/google/uuid" + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/mq" + "github.com/gravitl/netmaker/servercfg" + "golang.org/x/exp/slog" +) + +func gwHandlers(r *mux.Router) { + r.HandleFunc("/api/nodes/{network}/{nodeid}/gateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/gateway", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete) + // old relay handlers + r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createGateway))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete) +} + +// @Summary Create a gateway +// @Router /api/nodes/{network}/{nodeid}/gateway [post] +// @Tags Nodes +// @Security oauth2 +// @Success 200 {object} models.ApiNode +// @Failure 500 {object} models.ErrorResponse +func createGateway(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + nodeid := params["nodeid"] + netid := params["network"] + node, err := logic.ValidateParams(nodeid, netid) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + var req models.CreateGwReq + err = json.NewDecoder(r.Body).Decode(&req) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + node, err = logic.CreateIngressGateway(netid, nodeid, req.IngressRequest) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to create gateway on node [%s] on network [%s]: %v", + nodeid, netid, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + req.RelayRequest.NetID = netid + req.RelayRequest.NodeID = nodeid + _, relayNode, err := logic.CreateRelay(req.RelayRequest) + if err != nil { + logger.Log( + 0, + r.Header.Get("user"), + fmt.Sprintf( + "failed to create relay on node [%s] on network [%s]: %v", + req.RelayRequest.NodeID, + req.RelayRequest.NetID, + err, + ), + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, relayedNodeID := range relayNode.RelayedNodes { + relayedNode, err := logic.GetNodeByID(relayedNodeID) + if err == nil { + if relayedNode.FailedOverBy != uuid.Nil { + go logic.ResetFailedOverPeer(&relayedNode) + } + + } + } + logger.Log( + 1, + r.Header.Get("user"), + "created gw node", + req.RelayRequest.NodeID, + "on network", + req.RelayRequest.NetID, + ) + apiNode := relayNode.ConvertToAPINode() + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(apiNode) + go func() { + if err := mq.NodeUpdate(&node); err != nil { + slog.Error("error publishing node update to node", "node", node.ID, "error", err) + } + mq.PublishPeerUpdate(false) + }() + +} + +// @Summary Delete a gateway +// @Router /api/nodes/{network}/{nodeid}/gateway [delete] +// @Tags Nodes +// @Security oauth2 +// @Success 200 {object} models.ApiNode +// @Failure 500 {object} models.ErrorResponse +func deleteGateway(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + var params = mux.Vars(r) + nodeid := params["nodeid"] + netid := params["network"] + node, err := logic.ValidateParams(nodeid, netid) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + node, removedClients, err := logic.DeleteIngressGateway(nodeid) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to delete ingress gateway on node [%s] on network [%s]: %v", + nodeid, netid, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + updateNodes, node, err := logic.DeleteRelay(netid, nodeid) + if err != nil { + logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + node, err = logic.GetNodeByID(node.ID.String()) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to get node", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + node.IsGw = false + logic.UpsertNode(&node) + logger.Log(1, r.Header.Get("user"), "deleted gw", nodeid, "on network", netid) + + go func() { + host, err := logic.GetHost(node.HostID.String()) + if err == nil { + allNodes, err := logic.GetAllNodes() + if err != nil { + return + } + + for _, relayedNode := range updateNodes { + err = mq.NodeUpdate(&relayedNode) + if err != nil { + logger.Log( + 1, + "relayed node update ", + relayedNode.ID.String(), + "on network", + relayedNode.Network, + ": ", + err.Error(), + ) + + } + h, err := logic.GetHost(relayedNode.HostID.String()) + if err == nil { + if h.OS == models.OS_Types.IoT { + nodes, err := logic.GetAllNodes() + if err != nil { + return + } + node.IsRelay = true // for iot update to recognise that it has to delete relay peer + if err = mq.PublishSingleHostPeerUpdate(h, nodes, &node, nil, false, nil); err != nil { + logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error()) + } + } + } + } + if len(removedClients) > 0 { + if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:], false, nil); err != nil { + slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err) + } + } + mq.PublishPeerUpdate(false) + if err := mq.NodeUpdate(&node); err != nil { + slog.Error( + "error publishing node update to node", + "node", + node.ID, + "error", + err, + ) + } + if servercfg.IsDNSMode() { + logic.SetDNS() + } + + } + + }() + + apiNode := node.ConvertToAPINode() + logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(apiNode) +} diff --git a/controllers/node.go b/controllers/node.go index 904f6375..0bb6c13e 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -28,8 +28,8 @@ func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost) r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete) - r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost) - r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete) + r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost) + r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost) r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost) } @@ -548,113 +548,6 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) { }() } -// == INGRESS == - -// @Summary Create an remote access gateway -// @Router /api/nodes/{network}/{nodeid}/createingress [post] -// @Tags Nodes -// @Security oauth2 -// @Success 200 {object} models.ApiNode -// @Failure 500 {object} models.ErrorResponse -func createIngressGateway(w http.ResponseWriter, r *http.Request) { - var params = mux.Vars(r) - w.Header().Set("Content-Type", "application/json") - nodeid := params["nodeid"] - netid := params["network"] - node, err := logic.ValidateParams(nodeid, netid) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - var request models.IngressRequest - json.NewDecoder(r.Body).Decode(&request) - node, err = logic.CreateIngressGateway(netid, nodeid, request) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to create ingress gateway on node [%s] on network [%s]: %v", - nodeid, netid, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - apiNode := node.ConvertToAPINode() - logger.Log( - 1, - r.Header.Get("user"), - "created ingress gateway on node", - nodeid, - "on network", - netid, - ) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) - go func() { - if err := mq.NodeUpdate(&node); err != nil { - slog.Error("error publishing node update to node", "node", node.ID, "error", err) - } - mq.PublishPeerUpdate(false) - }() -} - -// @Summary Delete an remote access gateway -// @Router /api/nodes/{network}/{nodeid}/deleteingress [delete] -// @Tags Nodes -// @Security oauth2 -// @Success 200 {object} models.ApiNode -// @Failure 500 {object} models.ErrorResponse -func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - nodeid := params["nodeid"] - netid := params["network"] - node, err := logic.ValidateParams(nodeid, netid) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - node, removedClients, err := logic.DeleteIngressGateway(nodeid) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("failed to delete ingress gateway on node [%s] on network [%s]: %v", - nodeid, netid, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - - apiNode := node.ConvertToAPINode() - logger.Log(1, r.Header.Get("user"), "deleted ingress gateway", nodeid) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) - - if len(removedClients) > 0 { - host, err := logic.GetHost(node.HostID.String()) - if err == nil { - allNodes, err := logic.GetAllNodes() - if err != nil { - return - } - go func() { - if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:], false, nil); err != nil { - slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err) - } - mq.PublishPeerUpdate(false) - if err := mq.NodeUpdate(&node); err != nil { - slog.Error( - "error publishing node update to node", - "node", - node.ID, - "error", - err, - ) - } - if servercfg.IsDNSMode() { - logic.SetDNS() - } - }() - } - } -} - // @Summary Update an individual node // @Router /api/nodes/{network}/{nodeid} [put] // @Tags Nodes diff --git a/logic/gateway.go b/logic/gateway.go index a92682c5..3b96fc39 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -141,14 +141,14 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq return models.Node{}, err } if node.IsRelayed { - return models.Node{}, errors.New("ingress cannot be created on a relayed node") + return models.Node{}, errors.New("gateway cannot be created on a relayed node") } host, err := GetHost(node.HostID.String()) if err != nil { return models.Node{}, err } if host.OS != "linux" { - return models.Node{}, errors.New("ingress can only be created on linux based node") + return models.Node{}, errors.New("gateway can only be created on linux based node") } network, err := GetParentNetwork(netid) @@ -156,12 +156,16 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq return models.Node{}, err } node.IsIngressGateway = true + node.IsGw = true if !servercfg.IsPro { node.IsInternetGateway = ingress.IsInternetGateway } node.IngressGatewayRange = network.AddressRange node.IngressGatewayRange6 = network.AddressRange6 node.IngressDNS = ingress.ExtclientDNS + if node.IsInternetGateway && node.IngressDNS == "" { + node.IngressDNS = "1.1.1.1" + } node.IngressPersistentKeepalive = 20 if ingress.PersistentKeepalive != 0 { node.IngressPersistentKeepalive = ingress.PersistentKeepalive diff --git a/logic/peers.go b/logic/peers.go index b665e51f..f5d548ba 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -222,21 +222,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...) } _, isFailOverPeer := node.FailOverPeers[peer.ID.String()] - if servercfg.IsPro { - if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || - (peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer { - // if node is relayed and peer is not the relay, set remove to true - if _, ok := peerIndexMap[peerHost.PublicKey.String()]; ok { - continue - } - peerConfig.Remove = true - hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig) - peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1 + if (node.IsRelayed && node.RelayedBy != peer.ID.String()) || + (peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer { + // if node is relayed and peer is not the relay, set remove to true + if _, ok := peerIndexMap[peerHost.PublicKey.String()]; ok { continue } - if node.IsRelayed && node.RelayedBy == peer.ID.String() { - hostPeerUpdate = SetDefaultGwForRelayedUpdate(node, peer, hostPeerUpdate) - } + peerConfig.Remove = true + hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig) + peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1 + continue + } + if node.IsRelayed && node.RelayedBy == peer.ID.String() { + hostPeerUpdate = SetDefaultGwForRelayedUpdate(node, peer, hostPeerUpdate) } uselocal := false diff --git a/logic/pro/failover b/logic/pro/failover new file mode 100644 index 00000000..e69de29b diff --git a/logic/relay.go b/logic/relay.go index bd3c80bb..94262cc5 100644 --- a/logic/relay.go +++ b/logic/relay.go @@ -1,34 +1,246 @@ package logic import ( + "errors" + "fmt" "net" + "github.com/google/uuid" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic/acls/nodeacls" "github.com/gravitl/netmaker/models" ) -var GetRelays = func() ([]models.Node, error) { - return []models.Node{}, nil +// GetRelays - gets all the nodes that are relays +func GetRelays() ([]models.Node, error) { + nodes, err := GetAllNodes() + if err != nil { + return nil, err + } + relays := make([]models.Node, 0) + for _, node := range nodes { + if node.IsRelay { + relays = append(relays, node) + } + } + return relays, nil } -var RelayedAllowedIPs = func(peer, node *models.Node) []net.IPNet { - return []net.IPNet{} +// CreateRelay - creates a relay +func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) { + var returnnodes []models.Node + + node, err := GetNodeByID(relay.NodeID) + if err != nil { + return returnnodes, models.Node{}, err + } + host, err := GetHost(node.HostID.String()) + if err != nil { + return returnnodes, models.Node{}, err + } + if host.OS != "linux" { + return returnnodes, models.Node{}, fmt.Errorf("only linux machines can be gateway nodes") + } + err = ValidateRelay(relay, false) + if err != nil { + return returnnodes, models.Node{}, err + } + node.IsRelay = true + node.IsGw = true + node.RelayedNodes = relay.RelayedNodes + node.SetLastModified() + err = UpsertNode(&node) + if err != nil { + return returnnodes, node, err + } + returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes) + return returnnodes, node, nil } -var GetAllowedIpsForRelayed = func(relayed, relay *models.Node) []net.IPNet { - return []net.IPNet{} +// SetRelayedNodes- sets and saves node as relayed +func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node { + var returnnodes []models.Node + for _, id := range relayed { + node, err := GetNodeByID(id) + if err != nil { + logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error()) + continue + } + node.IsRelayed = setRelayed + if setRelayed { + node.RelayedBy = relay + } else { + node.RelayedBy = "" + } + node.SetLastModified() + if err := UpsertNode(&node); err != nil { + logger.Log(0, "setRelayedNodes.Insert", err.Error()) + continue + } + returnnodes = append(returnnodes, node) + } + return returnnodes } -var UpdateRelayed = func(currentNode, newNode *models.Node) { +// func GetRelayedNodes(relayNode *models.Node) (models.Node, error) { +// var returnnodes []models.Node +// networkNodes, err := GetNetworkNodes(relayNode.Network) +// if err != nil { +// return returnnodes, err +// } +// for _, node := range networkNodes { +// for _, addr := range relayNode.RelayAddrs { +// if addr == node.Address.IP.String() || addr == node.Address6.IP.String() { +// returnnodes = append(returnnodes, node) +// } +// } +// } +// return returnnodes, nil +// } + +// ValidateRelay - checks if relay is valid +func ValidateRelay(relay models.RelayRequest, update bool) error { + var err error + + node, err := GetNodeByID(relay.NodeID) + if err != nil { + return err + } + if !update && node.IsRelay { + return errors.New("node is already acting as a relay") + } + for _, relayedNodeID := range relay.RelayedNodes { + relayedNode, err := GetNodeByID(relayedNodeID) + if err != nil { + return err + } + if relayedNode.IsIngressGateway { + return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")") + } + if relayedNode.IsInternetGateway { + return errors.New("cannot relay an internet gateway (" + relayedNodeID + ")") + } + if relayedNode.InternetGwID != "" && relayedNode.InternetGwID != relay.NodeID { + return errors.New("cannot relay an internet client (" + relayedNodeID + ")") + } + if relayedNode.IsFailOver { + return errors.New("cannot relay a failOver (" + relayedNodeID + ")") + } + if relayedNode.FailedOverBy != uuid.Nil { + ResetFailedOverPeer(&relayedNode) + } + } + return err } -var SetRelayedNodes = func(setRelayed bool, relay string, relayed []string) []models.Node { - return []models.Node{} +// UpdateRelayNodes - updates relay nodes +func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []models.Node { + _ = SetRelayedNodes(false, relay, oldNodes) + return SetRelayedNodes(true, relay, newNodes) } -var RelayUpdates = func(currentNode, newNode *models.Node) bool { - return false +func RelayUpdates(currentNode, newNode *models.Node) bool { + relayUpdates := false + if newNode.IsRelay { + if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) { + relayUpdates = true + } else { + for i, node := range newNode.RelayedNodes { + if node != currentNode.RelayedNodes[i] { + relayUpdates = true + } + } + } + } + return relayUpdates } -var ValidateRelay = func(relay models.RelayRequest, update bool) error { - return nil +// UpdateRelayed - updates a relay's relayed nodes, and sends updates to the relayed nodes over MQ +func UpdateRelayed(currentNode, newNode *models.Node) { + updatenodes := updateRelayNodes(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes) + if len(updatenodes) > 0 { + for _, relayedNode := range updatenodes { + node := relayedNode + ResetFailedOverPeer(&node) + } + } +} + +// DeleteRelay - deletes a relay +func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) { + var returnnodes []models.Node + node, err := GetNodeByID(nodeid) + if err != nil { + return returnnodes, models.Node{}, err + } + returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes) + node.IsRelay = false + node.RelayedNodes = []string{} + node.SetLastModified() + if err = UpsertNode(&node); err != nil { + return returnnodes, models.Node{}, err + } + return returnnodes, node, nil +} + +func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet { + var allowedIPs = []net.IPNet{} + for _, relayedNodeID := range peer.RelayedNodes { + if node.ID.String() == relayedNodeID { + continue + } + relayedNode, err := GetNodeByID(relayedNodeID) + if err != nil { + continue + } + allowed := getRelayedAddresses(relayedNodeID) + if relayedNode.IsEgressGateway { + allowed = append(allowed, GetEgressIPs(&relayedNode)...) + } + allowedIPs = append(allowedIPs, allowed...) + } + return allowedIPs +} + +// GetAllowedIpsForRelayed - returns the peerConfig for a node relayed by relay +func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNet) { + if relayed.RelayedBy != relay.ID.String() { + logger.Log(0, "RelayedByRelay called with invalid parameters") + return + } + if relay.InternetGwID != "" { + return GetAllowedIpForInetNodeClient(relayed, relay) + } + peers, err := GetNetworkNodes(relay.Network) + if err != nil { + logger.Log(0, "error getting network clients", err.Error()) + return + } + for _, peer := range peers { + if peer.ID == relayed.ID || peer.ID == relay.ID { + continue + } + if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) { + allowedIPs = append(allowedIPs, GetAllowedIPs(relayed, &peer, nil)...) + } + } + return +} + +func getRelayedAddresses(id string) []net.IPNet { + addrs := []net.IPNet{} + node, err := GetNodeByID(id) + if err != nil { + logger.Log(0, "getRelayedAddresses: "+err.Error()) + return addrs + } + if node.Address.IP != nil { + node.Address.Mask = net.CIDRMask(32, 32) + addrs = append(addrs, node.Address) + } + if node.Address6.IP != nil { + node.Address6.Mask = net.CIDRMask(128, 128) + addrs = append(addrs, node.Address6) + } + return addrs } diff --git a/migrate/migrate.go b/migrate/migrate.go index 824deeca..24445f25 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -28,6 +28,7 @@ func Run() { updateHosts() updateNodes() updateAcls() + migrateToGws() } func assignSuperAdmin() { @@ -441,3 +442,18 @@ func createDefaultTagsAndPolicies() { } logic.MigrateAclPolicies() } + +func migrateToGws() { + nodes, err := logic.GetAllNodes() + if err != nil { + return + } + for _, node := range nodes { + if node.IsIngressGateway || node.IsRelay { + node.IsGw = true + node.IsIngressGateway = true + node.IsRelay = true + logic.UpsertNode(&node) + } + } +} diff --git a/models/gateway.go b/models/gateway.go new file mode 100644 index 00000000..6750c900 --- /dev/null +++ b/models/gateway.go @@ -0,0 +1,9 @@ +package models + +type CreateGwReq struct { + IngressRequest + RelayRequest +} + +type DeleteGw struct { +} diff --git a/models/node.go b/models/node.go index b97b35c2..48243540 100644 --- a/models/node.go +++ b/models/node.go @@ -78,11 +78,12 @@ type CommonNode struct { Action string `json:"action" yaml:"action"` LocalAddress net.IPNet `json:"localaddress" yaml:"localaddress"` IsEgressGateway bool `json:"isegressgateway" yaml:"isegressgateway"` - EgressGatewayRanges []string `json:"egressgatewayranges" yaml:"egressgatewayranges" bson:"egressgatewayranges"` + EgressGatewayRanges []string `json:"egressgatewayranges" yaml:"egressgatewayranges"` IsIngressGateway bool `json:"isingressgateway" yaml:"isingressgateway"` - IsRelayed bool `json:"isrelayed" yaml:"isrelayed" bson:"isrelayed"` - RelayedBy string `json:"relayedby" yaml:"relayedby" bson:"relayedby"` - IsRelay bool `json:"isrelay" yaml:"isrelay" bson:"isrelay"` + IsRelayed bool `json:"isrelayed" yaml:"isrelayed"` + RelayedBy string `json:"relayedby" yaml:"relayedby"` + IsRelay bool `json:"isrelay" yaml:"isrelay"` + IsGw bool `json:"is_gw" yaml:"is_gw"` RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"` IngressDNS string `json:"ingressdns" yaml:"ingressdns"` DNSOn bool `json:"dnson" yaml:"dnson"` diff --git a/pro/controllers/inet_gws.go b/pro/controllers/inet_gws.go index 22716514..d1cd8fd8 100644 --- a/pro/controllers/inet_gws.go +++ b/pro/controllers/inet_gws.go @@ -84,6 +84,9 @@ func createInternetGw(w http.ResponseWriter, r *http.Request) { }() } } + if node.IsGw && node.IngressDNS == "" { + node.IngressDNS = "1.1.1.1" + } err = logic.UpsertNode(&node) if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) diff --git a/pro/controllers/relay.go b/pro/controllers/relay.go deleted file mode 100644 index 415cf1cc..00000000 --- a/pro/controllers/relay.go +++ /dev/null @@ -1,152 +0,0 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/google/uuid" - proLogic "github.com/gravitl/netmaker/pro/logic" - - "github.com/gorilla/mux" - controller "github.com/gravitl/netmaker/controllers" - "github.com/gravitl/netmaker/logger" - "github.com/gravitl/netmaker/logic" - "github.com/gravitl/netmaker/models" - "github.com/gravitl/netmaker/mq" -) - -// RelayHandlers - handle Pro Relays -func RelayHandlers(r *mux.Router) { - - r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", logic.SecurityCheck(true, http.HandlerFunc(createRelay))).Methods(http.MethodPost) - r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", logic.SecurityCheck(true, http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete) - r.HandleFunc("/api/v1/host/{hostid}/failoverme", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))).Methods(http.MethodPost) -} - -// @Summary Create a relay -// @Router /api/nodes/{network}/{nodeid}/createrelay [post] -// @Tags PRO -// @Accept json -// @Produce json -// @Param network path string true "Network ID" -// @Param nodeid path string true "Node ID" -// @Param body body models.RelayRequest true "Relay request parameters" -// @Success 200 {object} models.ApiNode -// @Failure 400 {object} models.ErrorResponse -// @Failure 500 {object} models.ErrorResponse -func createRelay(w http.ResponseWriter, r *http.Request) { - var relayRequest models.RelayRequest - var params = mux.Vars(r) - w.Header().Set("Content-Type", "application/json") - err := json.NewDecoder(r.Body).Decode(&relayRequest) - if err != nil { - logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - relayRequest.NetID = params["network"] - relayRequest.NodeID = params["nodeid"] - _, relayNode, err := proLogic.CreateRelay(relayRequest) - if err != nil { - logger.Log( - 0, - r.Header.Get("user"), - fmt.Sprintf( - "failed to create relay on node [%s] on network [%s]: %v", - relayRequest.NodeID, - relayRequest.NetID, - err, - ), - ) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - for _, relayedNodeID := range relayNode.RelayedNodes { - relayedNode, err := logic.GetNodeByID(relayedNodeID) - if err == nil { - if relayedNode.FailedOverBy != uuid.Nil { - go logic.ResetFailedOverPeer(&relayedNode) - } - - } - } - go mq.PublishPeerUpdate(false) - logger.Log( - 1, - r.Header.Get("user"), - "created relay on node", - relayRequest.NodeID, - "on network", - relayRequest.NetID, - ) - apiNode := relayNode.ConvertToAPINode() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) -} - -// @Summary Remove a relay -// @Router /api/nodes/{network}/{nodeid}/deleterelay [delete] -// @Tags PRO -// @Accept json -// @Produce json -// @Param network path string true "Network ID" -// @Param nodeid path string true "Node ID" -// @Success 200 {object} models.ApiNode -// @Failure 400 {object} models.ErrorResponse -// @Failure 500 {object} models.ErrorResponse -func deleteRelay(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - nodeid := params["nodeid"] - netid := params["network"] - updateNodes, node, err := proLogic.DeleteRelay(netid, nodeid) - if err != nil { - logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - logger.Log(1, r.Header.Get("user"), "deleted relay server", nodeid, "on network", netid) - go func() { - for _, relayedNode := range updateNodes { - err = mq.NodeUpdate(&relayedNode) - if err != nil { - logger.Log( - 1, - "relayed node update ", - relayedNode.ID.String(), - "on network", - relayedNode.Network, - ": ", - err.Error(), - ) - - } - h, err := logic.GetHost(relayedNode.HostID.String()) - if err == nil { - if h.OS == models.OS_Types.IoT { - nodes, err := logic.GetAllNodes() - if err != nil { - return - } - node.IsRelay = true // for iot update to recognise that it has to delete relay peer - if err = mq.PublishSingleHostPeerUpdate(h, nodes, &node, nil, false, nil); err != nil { - logger.Log(1, "failed to publish peer update to host", h.ID.String(), ": ", err.Error()) - } - } - } - } - mq.PublishPeerUpdate(false) - }() - logger.Log( - 1, - r.Header.Get("user"), - "deleted relay on node", - node.ID.String(), - "on network", - node.Network, - ) - apiNode := node.ConvertToAPINode() - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(apiNode) -} diff --git a/pro/initialize.go b/pro/initialize.go index 55b9c868..6c6587f9 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -29,7 +29,6 @@ func InitPro() { controller.HttpHandlers = append( controller.HttpHandlers, proControllers.MetricHandlers, - proControllers.RelayHandlers, proControllers.UserHandlers, proControllers.FailOverHandlers, proControllers.InetHandlers, @@ -106,13 +105,6 @@ func InitPro() { logic.GetMetrics = proLogic.GetMetrics logic.UpdateMetrics = proLogic.UpdateMetrics logic.DeleteMetrics = proLogic.DeleteMetrics - logic.GetRelays = proLogic.GetRelays - logic.GetAllowedIpsForRelayed = proLogic.GetAllowedIpsForRelayed - logic.RelayedAllowedIPs = proLogic.RelayedAllowedIPs - logic.UpdateRelayed = proLogic.UpdateRelayed - logic.SetRelayedNodes = proLogic.SetRelayedNodes - logic.RelayUpdates = proLogic.RelayUpdates - logic.ValidateRelay = proLogic.ValidateRelay logic.GetTrialEndDate = getTrialEndDate logic.SetDefaultGw = proLogic.SetDefaultGw logic.SetDefaultGwForRelayedUpdate = proLogic.SetDefaultGwForRelayedUpdate diff --git a/pro/logic/relays.go b/pro/logic/relays.go deleted file mode 100644 index adb9bc6b..00000000 --- a/pro/logic/relays.go +++ /dev/null @@ -1,255 +0,0 @@ -package logic - -import ( - "errors" - "fmt" - "net" - - "github.com/google/uuid" - "github.com/gravitl/netmaker/logger" - "github.com/gravitl/netmaker/logic" - "github.com/gravitl/netmaker/logic/acls/nodeacls" - "github.com/gravitl/netmaker/models" - "github.com/gravitl/netmaker/mq" - "github.com/gravitl/netmaker/servercfg" - "golang.org/x/exp/slog" -) - -// GetRelays - gets all the nodes that are relays -func GetRelays() ([]models.Node, error) { - nodes, err := logic.GetAllNodes() - if err != nil { - return nil, err - } - relays := make([]models.Node, 0) - for _, node := range nodes { - if node.IsRelay { - relays = append(relays, node) - } - } - return relays, nil -} - -// CreateRelay - creates a relay -func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error) { - var returnnodes []models.Node - - node, err := logic.GetNodeByID(relay.NodeID) - if err != nil { - return returnnodes, models.Node{}, err - } - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - return returnnodes, models.Node{}, err - } - if host.OS != "linux" { - return returnnodes, models.Node{}, fmt.Errorf("only linux machines can be relay nodes") - } - err = ValidateRelay(relay, false) - if err != nil { - return returnnodes, models.Node{}, err - } - node.IsRelay = true - node.RelayedNodes = relay.RelayedNodes - node.SetLastModified() - err = logic.UpsertNode(&node) - if err != nil { - return returnnodes, node, err - } - returnnodes = SetRelayedNodes(true, relay.NodeID, relay.RelayedNodes) - return returnnodes, node, nil -} - -// SetRelayedNodes- sets and saves node as relayed -func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.Node { - var returnnodes []models.Node - for _, id := range relayed { - node, err := logic.GetNodeByID(id) - if err != nil { - logger.Log(0, "setRelayedNodes.GetNodebyID", err.Error()) - continue - } - node.IsRelayed = setRelayed - if setRelayed { - node.RelayedBy = relay - } else { - node.RelayedBy = "" - } - node.SetLastModified() - if err := logic.UpsertNode(&node); err != nil { - logger.Log(0, "setRelayedNodes.Insert", err.Error()) - continue - } - returnnodes = append(returnnodes, node) - } - return returnnodes -} - -// func GetRelayedNodes(relayNode *models.Node) (models.Node, error) { -// var returnnodes []models.Node -// networkNodes, err := GetNetworkNodes(relayNode.Network) -// if err != nil { -// return returnnodes, err -// } -// for _, node := range networkNodes { -// for _, addr := range relayNode.RelayAddrs { -// if addr == node.Address.IP.String() || addr == node.Address6.IP.String() { -// returnnodes = append(returnnodes, node) -// } -// } -// } -// return returnnodes, nil -// } - -// ValidateRelay - checks if relay is valid -func ValidateRelay(relay models.RelayRequest, update bool) error { - var err error - - node, err := logic.GetNodeByID(relay.NodeID) - if err != nil { - return err - } - if !update && node.IsRelay { - return errors.New("node is already acting as a relay") - } - for _, relayedNodeID := range relay.RelayedNodes { - relayedNode, err := logic.GetNodeByID(relayedNodeID) - if err != nil { - return err - } - if relayedNode.IsIngressGateway { - return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")") - } - if relayedNode.IsInternetGateway { - return errors.New("cannot relay an internet gateway (" + relayedNodeID + ")") - } - if relayedNode.InternetGwID != "" && relayedNode.InternetGwID != relay.NodeID { - return errors.New("cannot relay an internet client (" + relayedNodeID + ")") - } - if relayedNode.IsFailOver { - return errors.New("cannot relay a failOver (" + relayedNodeID + ")") - } - if relayedNode.FailedOverBy != uuid.Nil { - ResetFailedOverPeer(&relayedNode) - } - } - return err -} - -// UpdateRelayNodes - updates relay nodes -func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []models.Node { - _ = SetRelayedNodes(false, relay, oldNodes) - return SetRelayedNodes(true, relay, newNodes) -} - -func RelayUpdates(currentNode, newNode *models.Node) bool { - relayUpdates := false - if servercfg.IsPro && newNode.IsRelay { - if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) { - relayUpdates = true - } else { - for i, node := range newNode.RelayedNodes { - if node != currentNode.RelayedNodes[i] { - relayUpdates = true - } - } - } - } - return relayUpdates -} - -// UpdateRelayed - updates a relay's relayed nodes, and sends updates to the relayed nodes over MQ -func UpdateRelayed(currentNode, newNode *models.Node) { - updatenodes := updateRelayNodes(currentNode.ID.String(), currentNode.RelayedNodes, newNode.RelayedNodes) - if len(updatenodes) > 0 { - for _, relayedNode := range updatenodes { - node := relayedNode - ResetFailedOverPeer(&node) - go func() { - if err := mq.NodeUpdate(&node); err != nil { - slog.Error("error publishing node update to node", "node", node.ID, "error", err) - } - - }() - } - } -} - -// DeleteRelay - deletes a relay -func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) { - var returnnodes []models.Node - node, err := logic.GetNodeByID(nodeid) - if err != nil { - return returnnodes, models.Node{}, err - } - returnnodes = SetRelayedNodes(false, nodeid, node.RelayedNodes) - node.IsRelay = false - node.RelayedNodes = []string{} - node.SetLastModified() - if err = logic.UpsertNode(&node); err != nil { - return returnnodes, models.Node{}, err - } - return returnnodes, node, nil -} - -func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet { - var allowedIPs = []net.IPNet{} - for _, relayedNodeID := range peer.RelayedNodes { - if node.ID.String() == relayedNodeID { - continue - } - relayedNode, err := logic.GetNodeByID(relayedNodeID) - if err != nil { - continue - } - allowed := getRelayedAddresses(relayedNodeID) - if relayedNode.IsEgressGateway { - allowed = append(allowed, logic.GetEgressIPs(&relayedNode)...) - } - allowedIPs = append(allowedIPs, allowed...) - } - return allowedIPs -} - -// GetAllowedIpsForRelayed - returns the peerConfig for a node relayed by relay -func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNet) { - if relayed.RelayedBy != relay.ID.String() { - logger.Log(0, "RelayedByRelay called with invalid parameters") - return - } - if relay.InternetGwID != "" { - return GetAllowedIpForInetNodeClient(relayed, relay) - } - peers, err := logic.GetNetworkNodes(relay.Network) - if err != nil { - logger.Log(0, "error getting network clients", err.Error()) - return - } - for _, peer := range peers { - if peer.ID == relayed.ID || peer.ID == relay.ID { - continue - } - if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) { - allowedIPs = append(allowedIPs, logic.GetAllowedIPs(relayed, &peer, nil)...) - } - } - return -} - -func getRelayedAddresses(id string) []net.IPNet { - addrs := []net.IPNet{} - node, err := logic.GetNodeByID(id) - if err != nil { - logger.Log(0, "getRelayedAddresses: "+err.Error()) - return addrs - } - if node.Address.IP != nil { - node.Address.Mask = net.CIDRMask(32, 32) - addrs = append(addrs, node.Address) - } - if node.Address6.IP != nil { - node.Address6.Mask = net.CIDRMask(128, 128) - addrs = append(addrs, node.Address6) - } - return addrs -} From cec48be3543757bb19bc7a2db9da32dabc8fe916 Mon Sep 17 00:00:00 2001 From: Vishal Dalwadi <51291657+VishalDalwadi@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:33:38 +0530 Subject: [PATCH 05/12] NET-1906: Add endpoints for Sync All Hosts and Upgrade All Hosts. (#3302) * feat(go): add endpoint to sync all hosts. * feat(go): add endpoint to upgrade all hosts. * feat(go): allow force upgrade of hosts. * fix(go): config yaml tag. --- config/config.go | 2 +- controllers/hosts.go | 99 +++++++++++++++++++++++++++++++++++++++++++- models/host.go | 2 + 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 36ce1cbf..06e8ee39 100644 --- a/config/config.go +++ b/config/config.go @@ -37,7 +37,7 @@ type ServerConfig struct { APIConnString string `yaml:"apiconn"` APIHost string `yaml:"apihost"` APIPort string `yaml:"apiport"` - Broker string `yam:"broker"` + Broker string `yaml:"broker"` ServerBrokerEndpoint string `yaml:"serverbrokerendpoint"` BrokerType string `yaml:"brokertype"` EmqxRestEndpoint string `yaml:"emqxrestendpoint"` diff --git a/controllers/hosts.go b/controllers/hosts.go index 1346b654..5458a179 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -24,6 +24,10 @@ func hostHandlers(r *mux.Router) { Methods(http.MethodGet) r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))). Methods(http.MethodPut) + r.HandleFunc("/api/hosts/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHosts))). + Methods(http.MethodPost) + r.HandleFunc("/api/hosts/upgrade", logic.SecurityCheck(true, http.HandlerFunc(upgradeHosts))). + Methods(http.MethodPost) r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))). Methods(http.MethodPut) r.HandleFunc("/api/hosts/{hostid}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncHost))). @@ -50,11 +54,57 @@ func hostHandlers(r *mux.Router) { r.HandleFunc("/api/v1/auth-register/host", socketHandler) } +// @Summary Requests all the hosts to upgrade their version +// @Router /api/hosts/upgrade [post] +// @Tags Hosts +// @Security oauth +// @Param force query bool false "Force upgrade" +// @Success 200 {string} string "upgrade all hosts request received" +func upgradeHosts(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + action := models.Upgrade + + if r.URL.Query().Get("force") == "true" { + action = models.ForceUpgrade + } + + user := r.Header.Get("user") + + go func() { + slog.Info("requesting all hosts to upgrade", "user", user) + + hosts, err := logic.GetAllHosts() + if err != nil { + slog.Error("failed to retrieve all hosts", "user", user, "error", err) + return + } + + for _, host := range hosts { + go func(host models.Host) { + hostUpdate := models.HostUpdate{ + Action: action, + Host: host, + } + if err = mq.HostUpdate(&hostUpdate); err != nil { + slog.Error("failed to request host to upgrade", "user", user, "host", host.ID.String(), "error", err) + } else { + slog.Info("host upgrade requested", "user", user, "host", host.ID.String()) + } + }(host) + } + }() + + slog.Info("upgrade all hosts request received", "user", user) + logic.ReturnSuccessResponse(w, r, "upgrade all hosts request received") +} + // @Summary Upgrade a host // @Router /api/hosts/{hostid}/upgrade [put] // @Tags Hosts // @Security oauth // @Param hostid path string true "Host ID" +// @Param force query bool false "Force upgrade" // @Success 200 {string} string "passed message to upgrade host" // @Failure 500 {object} models.ErrorResponse // upgrade host is a handler to send upgrade message to a host @@ -65,7 +115,14 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "notfound")) return } - if err := mq.HostUpdate(&models.HostUpdate{Action: models.Upgrade, Host: *host}); err != nil { + + action := models.Upgrade + + if r.URL.Query().Get("force") == "true" { + action = models.ForceUpgrade + } + + if err := mq.HostUpdate(&models.HostUpdate{Action: action, Host: *host}); err != nil { slog.Error("failed to upgrade host", "error", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return @@ -860,6 +917,44 @@ func updateKeys(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +// @Summary Requests all the hosts to pull +// @Router /api/hosts/sync [post] +// @Tags Hosts +// @Security oauth +// @Success 200 {string} string "sync all hosts request received" +func syncHosts(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + user := r.Header.Get("user") + + go func() { + slog.Info("requesting all hosts to sync", "user", user) + + hosts, err := logic.GetAllHosts() + if err != nil { + slog.Error("failed to retrieve all hosts", "user", user, "error", err) + return + } + + for _, host := range hosts { + go func(host models.Host) { + hostUpdate := models.HostUpdate{ + Action: models.RequestPull, + Host: host, + } + if err = mq.HostUpdate(&hostUpdate); err != nil { + slog.Error("failed to request host to sync", "user", user, "host", host.ID.String(), "error", err) + } else { + slog.Info("host sync requested", "user", user, "host", host.ID.String()) + } + }(host) + } + }() + + slog.Info("sync all hosts request received", "user", user) + logic.ReturnSuccessResponse(w, r, "sync all hosts request received") +} + // @Summary Requests a host to pull // @Router /api/hosts/{hostid}/sync [post] // @Tags Hosts @@ -892,7 +987,7 @@ func syncHost(w http.ResponseWriter, r *http.Request) { } }() - slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID) + slog.Info("requested host pull", "user", r.Header.Get("user"), "host", host.ID.String()) w.WriteHeader(http.StatusOK) } diff --git a/models/host.go b/models/host.go index c6d5eaa3..a1a4c1b9 100644 --- a/models/host.go +++ b/models/host.go @@ -98,6 +98,8 @@ type HostMqAction string const ( // Upgrade - const to request host to update it's client Upgrade HostMqAction = "UPGRADE" + // ForceUpgrade - const for forcing a host to upgrade its client binary + ForceUpgrade HostMqAction = "FORCE_UPGRADE" // SignalHost - const for host signal action SignalHost HostMqAction = "SIGNAL_HOST" // UpdateHost - constant for host update action From 4431dc99a7c56c71511ecce111c75c9717fa52fc Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Mon, 3 Feb 2025 15:19:44 +0400 Subject: [PATCH 06/12] NET-1933: option to force destroy network (#3311) * option to force destroy network * fix network tests * fix network defaults func * fix network destroy action * delete network if node count is zero * push peer update network deletion * send node update --- controllers/hosts.go | 4 +-- controllers/network.go | 30 +++++++++++++++-- controllers/network_test.go | 21 ++++++++++-- logic/networks.go | 64 ++++++++++++++++++++++++++----------- logic/nodes.go | 2 +- models/network.go | 13 +++++++- serverctl/serverctl.go | 28 ++-------------- 7 files changed, 109 insertions(+), 53 deletions(-) diff --git a/controllers/hosts.go b/controllers/hosts.go index 5458a179..ddc04104 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -594,7 +594,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) { w, r, logic.FormatError( - fmt.Errorf("failed to force delete daemon node: "+err.Error()), + fmt.Errorf("failed to force delete daemon node: %s", err.Error()), "internal", ), ) @@ -634,7 +634,7 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) { w, r, logic.FormatError( - fmt.Errorf("failed to force delete daemon node: "+err.Error()), + fmt.Errorf("failed to force delete daemon node: %s", err.Error()), "internal", ), ) diff --git a/controllers/network.go b/controllers/network.go index 86a6e340..b1a308cc 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -434,6 +434,7 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) { // @Tags Networks // @Security oauth // @Param networkname path string true "Network name" +// @Param force query bool false "Force Delete" // @Produce json // @Success 200 {object} models.SuccessResponse // @Failure 400 {object} models.ErrorResponse @@ -441,10 +442,18 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) { func deleteNetwork(w http.ResponseWriter, r *http.Request) { // Set header w.Header().Set("Content-Type", "application/json") - + force := r.URL.Query().Get("force") == "true" var params = mux.Vars(r) network := params["networkname"] - err := logic.DeleteNetwork(network) + doneCh := make(chan struct{}, 1) + networkNodes, err := logic.GetNetworkNodes(network) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to get network nodes [%s]: %v", network, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + err = logic.DeleteNetwork(network, force, doneCh) if err != nil { errtype := "badrequest" if strings.Contains(err.Error(), "Node check failed") { @@ -459,7 +468,22 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) { go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network)) //delete network from allocated ip map go logic.RemoveNetworkFromAllocatedIpMap(network) - + go func() { + <-doneCh + mq.PublishPeerUpdate(true) + // send node update to clean up locally + for _, node := range networkNodes { + node := node + node.PendingDelete = true + node.Action = models.NODE_DELETE + if err := mq.NodeUpdate(&node); err != nil { + slog.Error("error publishing node update to node", "node", node.ID, "error", err) + } + } + if servercfg.IsDNSMode() { + logic.SetDNS() + } + }() logger.Log(1, r.Header.Get("user"), "deleted network", network) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode("success") diff --git a/controllers/network_test.go b/controllers/network_test.go index 4320b6e3..87cb6ee3 100644 --- a/controllers/network_test.go +++ b/controllers/network_test.go @@ -75,11 +75,19 @@ func TestDeleteNetwork(t *testing.T) { t.Run("NetworkwithNodes", func(t *testing.T) { }) t.Run("DeleteExistingNetwork", func(t *testing.T) { - err := logic.DeleteNetwork("skynet") + doneCh := make(chan struct{}, 1) + err := logic.DeleteNetwork("skynet", false, doneCh) assert.Nil(t, err) }) t.Run("NonExistentNetwork", func(t *testing.T) { - err := logic.DeleteNetwork("skynet") + doneCh := make(chan struct{}, 1) + err := logic.DeleteNetwork("skynet", false, doneCh) + assert.Nil(t, err) + }) + createNetv1("test") + t.Run("ForceDeleteNetwork", func(t *testing.T) { + doneCh := make(chan struct{}, 1) + err := logic.DeleteNetwork("test", true, doneCh) assert.Nil(t, err) }) } @@ -214,6 +222,15 @@ func createNet() { logic.CreateNetwork(network) } } +func createNetv1(netId string) { + var network models.Network + network.NetID = netId + network.AddressRange = "100.0.0.1/24" + _, err := logic.GetNetwork(netId) + if err != nil { + logic.CreateNetwork(network) + } +} func createNetDualStack() { var network models.Network diff --git a/logic/networks.go b/logic/networks.go index 1617889d..04b6b573 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -102,7 +102,7 @@ func RemoveIpFromAllocatedIpMap(networkName string, ip string) { // AddNetworkToAllocatedIpMap - add network to allocated ip map when network is added func AddNetworkToAllocatedIpMap(networkName string) { networkCacheMutex.Lock() - allocatedIpMap[networkName] = map[string]net.IP{} + allocatedIpMap[networkName] = make(map[string]net.IP) networkCacheMutex.Unlock() } @@ -171,23 +171,8 @@ func GetNetworks() ([]models.Network, error) { } // DeleteNetwork - deletes a network -func DeleteNetwork(network string) error { - // remove ACL for network - err := nodeacls.DeleteACLContainer(nodeacls.NetworkID(network)) - if err != nil { - logger.Log(1, "failed to remove the node acls during network delete for network,", network) - } - // Delete default network enrollment key - keys, _ := GetAllEnrollmentKeys() - for _, key := range keys { - if key.Tags[0] == network { - if key.Default { - DeleteEnrollmentKey(key.Value, true) - break - } +func DeleteNetwork(network string, force bool, done chan struct{}) error { - } - } nodeCount, err := GetNetworkNonServerNodeCount(network) if nodeCount == 0 || database.IsEmptyRecord(err) { // delete server nodes first then db records @@ -200,7 +185,50 @@ func DeleteNetwork(network string) error { } return nil } - return errors.New("node check failed. All nodes must be deleted before deleting network") + + // Remove All Nodes + go func() { + nodes, err := GetNetworkNodes(network) + if err == nil { + for _, node := range nodes { + node := node + host, err := GetHost(node.HostID.String()) + if err != nil { + continue + } + DissasociateNodeFromHost(&node, host) + } + } + // remove ACL for network + err = nodeacls.DeleteACLContainer(nodeacls.NetworkID(network)) + if err != nil { + logger.Log(1, "failed to remove the node acls during network delete for network,", network) + } + // delete server nodes first then db records + err = database.DeleteRecord(database.NETWORKS_TABLE_NAME, network) + if err != nil { + return + } + if servercfg.CacheEnabled() { + deleteNetworkFromCache(network) + } + done <- struct{}{} + close(done) + }() + + // Delete default network enrollment key + keys, _ := GetAllEnrollmentKeys() + for _, key := range keys { + if key.Tags[0] == network { + if key.Default { + DeleteEnrollmentKey(key.Value, true) + break + } + + } + } + + return nil } // CreateNetwork - creates a network in database diff --git a/logic/nodes.go b/logic/nodes.go index 368d7d67..0b0c2495 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -239,7 +239,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { } } - return fmt.Errorf("failed to update node " + currentNode.ID.String() + ", cannot change ID.") + return fmt.Errorf("failed to update node %s, cannot change ID", currentNode.ID.String()) } // DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node diff --git a/models/network.go b/models/network.go index dbb2fc4a..32707933 100644 --- a/models/network.go +++ b/models/network.go @@ -42,9 +42,10 @@ func (network *Network) SetNetworkLastModified() { } // Network.SetDefaults - sets default values for a network struct -func (network *Network) SetDefaults() { +func (network *Network) SetDefaults() (upsert bool) { if network.DefaultUDPHolePunch == "" { network.DefaultUDPHolePunch = "no" + upsert = true } if network.DefaultInterface == "" { if len(network.NetID) < 33 { @@ -52,35 +53,45 @@ func (network *Network) SetDefaults() { } else { network.DefaultInterface = network.NetID } + upsert = true } if network.DefaultListenPort == 0 { network.DefaultListenPort = 51821 + upsert = true } if network.NodeLimit == 0 { network.NodeLimit = 999999999 + upsert = true } if network.DefaultKeepalive == 0 { network.DefaultKeepalive = 20 + upsert = true } if network.AllowManualSignUp == "" { network.AllowManualSignUp = "no" + upsert = true } if network.IsIPv4 == "" { network.IsIPv4 = "yes" + upsert = true } if network.IsIPv6 == "" { network.IsIPv6 = "no" + upsert = true } if network.DefaultMTU == 0 { network.DefaultMTU = 1280 + upsert = true } if network.DefaultACL == "" { network.DefaultACL = "yes" + upsert = true } + return } func (network *Network) GetNetworkNetworkCIDR4() *net.IPNet { diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go index 3cedf72d..12fbc3e4 100644 --- a/serverctl/serverctl.go +++ b/serverctl/serverctl.go @@ -59,32 +59,8 @@ func setNetworkDefaults() error { return err } for _, network := range networks { - update := false - newNet := network - if strings.Contains(network.NetID, ".") { - newNet.NetID = strings.ReplaceAll(network.NetID, ".", "") - newNet.DefaultInterface = strings.ReplaceAll(network.DefaultInterface, ".", "") - update = true - } - if strings.ContainsAny(network.NetID, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { - newNet.NetID = strings.ToLower(network.NetID) - newNet.DefaultInterface = strings.ToLower(network.DefaultInterface) - update = true - } - if update { - newNet.SetDefaults() - if err := logic.SaveNetwork(&newNet); err != nil { - logger.Log(0, "error saving networks during initial update:", err.Error()) - } - if err := logic.DeleteNetwork(network.NetID); err != nil { - logger.Log(0, "error deleting old network:", err.Error()) - } - } else { - network.SetDefaults() - _, _, _, err = logic.UpdateNetwork(&network, &network) - if err != nil { - logger.Log(0, "could not set defaults on network", network.NetID) - } + if network.SetDefaults() { + logic.SaveNetwork(&network) } } return nil From 2668fcf1e58a7cafa1e301ca2b614743ed705d43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:19:58 +0400 Subject: [PATCH 07/12] Bump alpine from 3.21.0 to 3.21.2 (#3297) Bumps alpine from 3.21.0 to 3.21.2. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- Dockerfile-quick | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3dd1a0e9..667fe1f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY . . RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} . # RUN go build -tags=ee . -o netmaker main.go -FROM alpine:3.21.0 +FROM alpine:3.21.2 # add a c lib # set the working directory diff --git a/Dockerfile-quick b/Dockerfile-quick index 4180634a..cbb354b2 100644 --- a/Dockerfile-quick +++ b/Dockerfile-quick @@ -1,5 +1,5 @@ #first stage - builder -FROM alpine:3.21.0 +FROM alpine:3.21.2 ARG version WORKDIR /app COPY ./netmaker /root/netmaker From 04fe56db4f4f13f416040731d31d0d3a5d19d392 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:20:36 +0400 Subject: [PATCH 08/12] Bump github.com/go-playground/validator/v10 from 10.23.0 to 10.24.0 (#3296) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.23.0 to 10.24.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.23.0...v10.24.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b06a2b95..2ffafb9e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23 require ( github.com/blang/semver v3.5.1+incompatible github.com/eclipse/paho.mqtt.golang v1.4.3 - github.com/go-playground/validator/v10 v10.23.0 + github.com/go-playground/validator/v10 v10.24.0 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 @@ -18,10 +18,10 @@ require ( github.com/stretchr/testify v1.10.0 github.com/txn2/txeh v1.5.5 go.uber.org/automaxprocs v1.6.0 - golang.org/x/crypto v0.30.0 - golang.org/x/net v0.27.0 // indirect + golang.org/x/crypto v0.32.0 + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.24.0 - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb gopkg.in/yaml.v3 v3.0.1 @@ -50,7 +50,7 @@ require ( require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index a25549d4..da0ae967 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQ github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -27,8 +27,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= -github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -101,8 +101,8 @@ go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwE golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -112,8 +112,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -129,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= From e13bf2c0eb6019c790184a86ac57e8e576f0622d Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 4 Feb 2025 08:44:24 +0400 Subject: [PATCH 09/12] NET-1923: Add Metric Port to server config (#3306) * set default metrics port 8889 * set default metrics port 51821 * add metrics port to server config * bind caddy only on tcp * add var for pulling files * add new line * update peer update model * check if port is not zero * set replace peer to false on pull * do not replace peers on failover sync * remove debug log * add old peer update fields for backwards compatibility * add old json tag * add debug log in caller trace func --- compose/docker-compose.yml | 4 ++-- config/config.go | 13 ++++++------ controllers/hosts.go | 11 +++------- logic/peers.go | 14 +++++-------- logic/util.go | 25 ++++++++++++++++++++++ models/metrics.go | 1 + models/mqtt.go | 40 ++++++++++++++++++++---------------- models/structs.go | 38 ++++++++++++++++++---------------- mq/handlers.go | 1 + mq/publishers.go | 6 +++++- scripts/netmaker.default.env | 4 ++++ scripts/nm-quick.sh | 4 ++-- servercfg/serverconf.go | 17 +++++++++++++++ utils/utils.go | 22 +++++++++++++++++++- 14 files changed, 135 insertions(+), 65 deletions(-) diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 5e6b1cc7..054936af 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -52,8 +52,8 @@ services: - caddy_data:/data - caddy_conf:/config ports: - - "80:80" - - "443:443" + - "80:80/tcp" + - "443:443/tcp" coredns: #network_mode: host diff --git a/config/config.go b/config/config.go index 06e8ee39..dac7e186 100644 --- a/config/config.go +++ b/config/config.go @@ -92,14 +92,15 @@ type ServerConfig struct { JwtValidityDuration time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"` RacAutoDisable bool `yaml:"rac_auto_disable"` CacheEnabled string `yaml:"caching_enabled"` - EndpointDetection bool `json:"endpoint_detection"` + EndpointDetection bool `yaml:"endpoint_detection"` AllowedEmailDomains string `yaml:"allowed_email_domains"` - EmailSenderAddr string `json:"email_sender_addr"` - EmailSenderUser string `json:"email_sender_user"` - EmailSenderPassword string `json:"email_sender_password"` - SmtpHost string `json:"smtp_host"` - SmtpPort int `json:"smtp_port"` + EmailSenderAddr string `yaml:"email_sender_addr"` + EmailSenderUser string `yaml:"email_sender_user"` + EmailSenderPassword string `yaml:"email_sender_password"` + SmtpHost string `yaml:"smtp_host"` + SmtpPort int `yaml:"smtp_port"` MetricInterval string `yaml:"metric_interval"` + MetricsPort int `yaml:"metrics_port"` ManageDNS bool `yaml:"manage_dns"` Stun bool `yaml:"stun"` StunServers string `yaml:"stun_servers"` diff --git a/controllers/hosts.go b/controllers/hosts.go index ddc04104..abed0d3a 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "os" "github.com/google/uuid" "github.com/gorilla/mux" @@ -239,11 +238,7 @@ func pull(w http.ResponseWriter, r *http.Request) { } } if sendPeerUpdate { - reset := true - if os.Getenv("RESET_PEER_UPDATE") != "" { - reset = os.Getenv("RESET_PEER_UPDATE") == "true" - } - if err := mq.PublishPeerUpdate(reset); err != nil { + if err := mq.PublishPeerUpdate(false); err != nil { logger.Log(0, "fail to publish peer update: ", err.Error()) } } @@ -373,11 +368,11 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) { var hostUpdate models.HostUpdate err = json.NewDecoder(r.Body).Decode(&hostUpdate) if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error()) + slog.Error("failed to update a host:", "user", r.Header.Get("user"), "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID) + slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID, "action", hostUpdate.Action) switch hostUpdate.Action { case models.CheckIn: sendPeerUpdate = mq.HandleHostCheckin(&hostUpdate.Host, currentHost) diff --git a/logic/peers.go b/logic/peers.go index f5d548ba..31412100 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -79,11 +79,11 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N IngressInfo: make(map[string]models.IngressInfo), AclRules: make(map[string]models.AclRule), }, - PeerIDs: make(models.PeerMap, 0), - Peers: []wgtypes.PeerConfig{}, - NodePeers: []wgtypes.PeerConfig{}, - HostNetworkInfo: models.HostInfoMap{}, - EndpointDetection: servercfg.IsEndpointDetectionEnabled(), + PeerIDs: make(models.PeerMap, 0), + Peers: []wgtypes.PeerConfig{}, + NodePeers: []wgtypes.PeerConfig{}, + HostNetworkInfo: models.HostInfoMap{}, + ServerConfig: servercfg.ServerInfo, } defer func() { if !hostPeerUpdate.FwUpdate.AllowAll { @@ -457,10 +457,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N } } } - - hostPeerUpdate.ManageDNS = servercfg.GetManageDNS() - hostPeerUpdate.Stun = servercfg.IsStunEnabled() - hostPeerUpdate.StunServers = servercfg.GetStunServers() return hostPeerUpdate, nil } diff --git a/logic/util.go b/logic/util.go index bff2ced6..22ee3f09 100644 --- a/logic/util.go +++ b/logic/util.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "log/slog" "net" "os" "strings" @@ -91,6 +92,30 @@ func StringSliceContains(slice []string, item string) bool { } return false } +func SetVerbosity(logLevel int) { + var level slog.Level + switch logLevel { + + case 0: + level = slog.LevelInfo + case 1: + level = slog.LevelError + case 2: + level = slog.LevelWarn + case 3: + level = slog.LevelDebug + + default: + level = slog.LevelInfo + } + // Create the logger with the chosen level + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + }) + logger := slog.New(handler) + slog.SetDefault(logger) + +} // NormalizeCIDR - returns the first address of CIDR func NormalizeCIDR(address string) (string, error) { diff --git a/models/metrics.go b/models/metrics.go index 2e70a178..c364c18e 100644 --- a/models/metrics.go +++ b/models/metrics.go @@ -48,6 +48,7 @@ type HostNetworkInfo struct { ListenPort int `json:"listen_port" yaml:"listen_port"` IsStaticPort bool `json:"is_static_port"` IsStatic bool `json:"is_static"` + Version string `json:"version"` } // PeerMap - peer map for ids and addresses in metrics diff --git a/models/mqtt.go b/models/mqtt.go index c5921f38..4b7ce10a 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -8,25 +8,29 @@ import ( // HostPeerUpdate - struct for host peer updates type HostPeerUpdate struct { - Host Host `json:"host" bson:"host" yaml:"host"` - ChangeDefaultGw bool `json:"change_default_gw"` - DefaultGwIp net.IP `json:"default_gw_ip"` - IsInternetGw bool `json:"is_inet_gw"` - NodeAddrs []net.IPNet `json:"nodes_addrs" yaml:"nodes_addrs"` - Server string `json:"server" bson:"server" yaml:"server"` - ServerVersion string `json:"serverversion" bson:"serverversion" yaml:"serverversion"` - ServerAddrs []ServerAddr `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"` + Host Host `json:"host"` + ChangeDefaultGw bool `json:"change_default_gw"` + DefaultGwIp net.IP `json:"default_gw_ip"` + IsInternetGw bool `json:"is_inet_gw"` + NodeAddrs []net.IPNet `json:"nodes_addrs"` + Server string `json:"server"` + ServerVersion string `json:"serverversion"` + ServerAddrs []ServerAddr `json:"serveraddrs"` + NodePeers []wgtypes.PeerConfig `json:"node_peers"` + Peers []wgtypes.PeerConfig `json:"host_peers"` + PeerIDs PeerMap `json:"peerids"` + HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty"` + EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"` + FwUpdate FwUpdate `json:"fw_update"` + ReplacePeers bool `json:"replace_peers"` + ServerConfig + OldPeerUpdateFields +} + +type OldPeerUpdateFields struct { NodePeers []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"` - Peers []wgtypes.PeerConfig - PeerIDs PeerMap `json:"peerids" bson:"peerids" yaml:"peerids"` - HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"` - EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"` - FwUpdate FwUpdate `json:"fw_update"` - ReplacePeers bool `json:"replace_peers"` - EndpointDetection bool `json:"endpoint_detection"` - ManageDNS bool `yaml:"manage_dns"` - Stun bool `yaml:"stun"` - StunServers string `yaml:"stun_servers"` + OldPeers []wgtypes.PeerConfig `json:"Peers"` + EndpointDetection bool `json:"endpoint_detection"` } type FwRule struct { diff --git a/models/structs.go b/models/structs.go index e929e7fc..49655a6e 100644 --- a/models/structs.go +++ b/models/structs.go @@ -252,24 +252,26 @@ type NodeJoinResponse struct { // ServerConfig - struct for dealing with the server information for a netclient type ServerConfig struct { - CoreDNSAddr string `yaml:"corednsaddr"` - API string `yaml:"api"` - APIPort string `yaml:"apiport"` - DNSMode string `yaml:"dnsmode"` - Version string `yaml:"version"` - MQPort string `yaml:"mqport"` - MQUserName string `yaml:"mq_username"` - MQPassword string `yaml:"mq_password"` - BrokerType string `yaml:"broker_type"` - Server string `yaml:"server"` - Broker string `yaml:"broker"` - IsPro bool `yaml:"isee" json:"Is_EE"` - TrafficKey []byte `yaml:"traffickey"` - MetricInterval string `yaml:"metric_interval"` - ManageDNS bool `yaml:"manage_dns"` - Stun bool `yaml:"stun"` - StunServers string `yaml:"stun_servers"` - DefaultDomain string `yaml:"default_domain"` + CoreDNSAddr string `yaml:"corednsaddr"` + API string `yaml:"api"` + APIPort string `yaml:"apiport"` + DNSMode string `yaml:"dnsmode"` + Version string `yaml:"version"` + MQPort string `yaml:"mqport"` + MQUserName string `yaml:"mq_username"` + MQPassword string `yaml:"mq_password"` + BrokerType string `yaml:"broker_type"` + Server string `yaml:"server"` + Broker string `yaml:"broker"` + IsPro bool `yaml:"isee" json:"Is_EE"` + TrafficKey []byte `yaml:"traffickey"` + MetricInterval string `yaml:"metric_interval"` + MetricsPort int `yaml:"metrics_port"` + ManageDNS bool `yaml:"manage_dns"` + Stun bool `yaml:"stun"` + StunServers string `yaml:"stun_servers"` + EndpointDetection bool `yaml:"endpoint_detection"` + DefaultDomain string `yaml:"default_domain"` } // User.NameInCharset - returns if name is in charset below or not diff --git a/mq/handlers.go b/mq/handlers.go index 12913326..3b33bb69 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -280,6 +280,7 @@ func HandleHostCheckin(h, currentHost *models.Host) bool { (h.ListenPort != 0 && h.ListenPort != currentHost.ListenPort) || (h.WgPublicListenPort != 0 && h.WgPublicListenPort != currentHost.WgPublicListenPort) || (!h.EndpointIPv6.Equal(currentHost.EndpointIPv6)) if ifaceDelta { // only save if something changes + currentHost.EndpointIP = h.EndpointIP currentHost.EndpointIPv6 = h.EndpointIPv6 currentHost.Interfaces = h.Interfaces diff --git a/mq/publishers.go b/mq/publishers.go index 28ef1935..cbebf60f 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -17,7 +17,6 @@ import ( // PublishPeerUpdate --- determines and publishes a peer update to all the hosts func PublishPeerUpdate(replacePeers bool) error { - if !servercfg.IsMessageQueueBackend() { return nil } @@ -114,6 +113,11 @@ func PublishSingleHostPeerUpdate(host *models.Host, allNodes []models.Node, dele if err != nil { return err } + peerUpdate.OldPeerUpdateFields = models.OldPeerUpdateFields{ + NodePeers: peerUpdate.NodePeers, + OldPeers: peerUpdate.Peers, + EndpointDetection: peerUpdate.ServerConfig.EndpointDetection, + } peerUpdate.ReplacePeers = replacePeers data, err := json.Marshal(&peerUpdate) if err != nil { diff --git a/scripts/netmaker.default.env b/scripts/netmaker.default.env index 806c3765..e5de8d35 100644 --- a/scripts/netmaker.default.env +++ b/scripts/netmaker.default.env @@ -96,3 +96,7 @@ MANAGE_DNS=false OLD_ACL_SUPPORT=true # if STUN is set to true, hole punch is called STUN=true +# Metrics Collection Port +METRICS_PORT=51821 +# Metrics Collection interval in minutes +PUBLISH_METRIC_INTERVAL=15 diff --git a/scripts/nm-quick.sh b/scripts/nm-quick.sh index 86a083c5..3c79dff8 100755 --- a/scripts/nm-quick.sh +++ b/scripts/nm-quick.sh @@ -6,7 +6,7 @@ SCRIPT_DIR=$(dirname "$(realpath "$0")") CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE" NM_QUICK_VERSION="0.1.1" LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\") - +BRANCH=master if [ $(id -u) -ne 0 ]; then echo "This script must be run as root" exit 1 @@ -617,7 +617,7 @@ install_netmaker() { echo "Pulling config files..." - local BASE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master" + local BASE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BRANCH" local COMPOSE_URL="$BASE_URL/compose/docker-compose.yml" local CADDY_URL="$BASE_URL/docker/Caddyfile" if [ "$INSTALL_TYPE" = "pro" ]; then diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 267a08bf..f00723af 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -14,6 +14,8 @@ import ( "github.com/gravitl/netmaker/models" ) +var ServerInfo = GetServerInfo() + // EmqxBrokerType denotes the broker type for EMQX MQTT const EmqxBrokerType = "emqx" @@ -141,10 +143,12 @@ func GetServerInfo() models.ServerConfig { cfg.Version = GetVersion() cfg.IsPro = IsPro cfg.MetricInterval = GetMetricInterval() + cfg.MetricsPort = GetMetricsPort() cfg.ManageDNS = GetManageDNS() cfg.Stun = IsStunEnabled() cfg.StunServers = GetStunServers() cfg.DefaultDomain = GetDefaultDomain() + cfg.EndpointDetection = IsEndpointDetectionEnabled() return cfg } @@ -654,6 +658,19 @@ func GetMqUserName() string { return password } +// GetMetricsPort - get metrics port +func GetMetricsPort() int { + p := 51821 + if os.Getenv("METRICS_PORT") != "" { + pStr := os.Getenv("METRICS_PORT") + pInt, err := strconv.Atoi(pStr) + if err == nil && pInt != 0 { + p = pInt + } + } + return p +} + // GetMetricInterval - get the publish metric interval func GetMetricIntervalInMinutes() time.Duration { //default 15 minutes diff --git a/utils/utils.go b/utils/utils.go index cb8c41d8..f755dceb 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,10 @@ package utils -import "time" +import ( + "log/slog" + "runtime" + "time" +) // RetryStrategy specifies a strategy to retry an operation after waiting a while, // with hooks for successful and unsuccessful (>=max) tries. @@ -39,3 +43,19 @@ func (rs RetryStrategy) DoStrategy() { return } } + +func TraceCaller() { + // Skip 1 frame to get the caller of this function + pc, file, line, ok := runtime.Caller(2) + if !ok { + slog.Debug("Unable to get caller information") + return + } + + // Get function name from the program counter (pc) + funcName := runtime.FuncForPC(pc).Name() + + // Print trace details + slog.Debug("Called from function: %s\n", "func-name", funcName) + slog.Debug("File: %s, Line: %d\n", "file", file, "line-no", line) +} From 39006debba57091fb11158b03362650138439c38 Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 4 Feb 2025 09:01:36 +0400 Subject: [PATCH 10/12] add stun to server stack (#3312) --- compose/docker-compose.yml | 10 +++++++++- docker/Caddyfile | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 054936af..e1b427c4 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -12,7 +12,7 @@ services: - sqldata:/root/data environment: # config-dependant vars - - STUN_SERVERS=stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302 + - STUN_SERVERS=stun.${NM_DOMAIN}:3478,stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302 # The domain/host IP indicating the mq broker address - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN} # For EMQX broker use `BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}/mqtt` # For EMQX broker (uncomment the two lines below) @@ -39,6 +39,14 @@ services: links: - "netmaker:api" restart: always + stun: + container_name: stun + image: coturn/coturn + restart: always + ports: + - "3478:3478/udp" # STUN UDP + environment: + - LISTENING_PORT=3478 caddy: image: caddy:2.8.4 diff --git a/docker/Caddyfile b/docker/Caddyfile index 9f5b3565..c85decf3 100644 --- a/docker/Caddyfile +++ b/docker/Caddyfile @@ -32,3 +32,7 @@ broker.{$NM_DOMAIN} { } reverse_proxy @ws mq:8883 # For EMQX websockets use `reverse_proxy @ws mq:8083` } + +https://stun.{$NM_DOMAIN} { + reverse_proxy stun:3478 +} From 7b6e544cdc995ce8b98b62159493cc2c47c7485a Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Thu, 6 Feb 2025 13:25:31 +0400 Subject: [PATCH 11/12] Revert "add stun to server stack (#3312)" This reverts commit 39006debba57091fb11158b03362650138439c38. --- compose/docker-compose.yml | 10 +--------- docker/Caddyfile | 4 ---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index e1b427c4..054936af 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -12,7 +12,7 @@ services: - sqldata:/root/data environment: # config-dependant vars - - STUN_SERVERS=stun.${NM_DOMAIN}:3478,stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302 + - STUN_SERVERS=stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302 # The domain/host IP indicating the mq broker address - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN} # For EMQX broker use `BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}/mqtt` # For EMQX broker (uncomment the two lines below) @@ -39,14 +39,6 @@ services: links: - "netmaker:api" restart: always - stun: - container_name: stun - image: coturn/coturn - restart: always - ports: - - "3478:3478/udp" # STUN UDP - environment: - - LISTENING_PORT=3478 caddy: image: caddy:2.8.4 diff --git a/docker/Caddyfile b/docker/Caddyfile index c85decf3..9f5b3565 100644 --- a/docker/Caddyfile +++ b/docker/Caddyfile @@ -32,7 +32,3 @@ broker.{$NM_DOMAIN} { } reverse_proxy @ws mq:8883 # For EMQX websockets use `reverse_proxy @ws mq:8083` } - -https://stun.{$NM_DOMAIN} { - reverse_proxy stun:3478 -} From 689b9b6b52c7abf79ff9e4ba95a8428a3e4e739a Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Fri, 14 Feb 2025 16:06:26 +0400 Subject: [PATCH 12/12] NET-1941: failover signaling optimized, new endpoint for metrics peer info (#3322) * optimise acl cache with network map * fix mutex clash * fix acl mutex * add new endpoint for metrics peer info * fix static check * add new endpoint for metrics peer info * fix host peer info api params * cache failover data * add json tag * avoid duplicate calls to failover * add failover check ctx * add failover check ctx * fix failover debug log * optimise failover operations * remove debug logs * rm unused model * rm unused model --- Dockerfile | 2 + controllers/hosts.go | 36 ++++++++- logic/acls.go | 2 + logic/extpeers.go | 2 - logic/nodes.go | 6 +- logic/peers.go | 84 ++++++++++++++++++++- logic/pro/failover | 0 models/mqtt.go | 4 + pro/controllers/failover.go | 141 +++++++++++++++++++++++++++++++++++- pro/initialize.go | 1 + pro/logic/failover.go | 79 +++++++++++++++++--- 11 files changed, 331 insertions(+), 26 deletions(-) delete mode 100644 logic/pro/failover diff --git a/Dockerfile b/Dockerfile index 667fe1f1..179d9a6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ FROM alpine:3.21.2 # add a c lib # set the working directory WORKDIR /root/ +RUN apk update && apk upgrade +RUN apk add --no-cache sqlite RUN mkdir -p /etc/netclient/config COPY --from=builder /app/netmaker . COPY --from=builder /app/config config diff --git a/controllers/hosts.go b/controllers/hosts.go index abed0d3a..c17cf90a 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/google/uuid" "github.com/gorilla/mux" @@ -48,6 +49,8 @@ func hostHandlers(r *mux.Router) { Methods(http.MethodPost) r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))). Methods(http.MethodPut) + r.HandleFunc("/api/v1/host/{hostid}/peer_info", Authorize(true, false, "host", http.HandlerFunc(getHostPeerInfo))). + Methods(http.MethodGet) r.HandleFunc("/api/emqx/hosts", logic.SecurityCheck(true, http.HandlerFunc(delEmqxHosts))). Methods(http.MethodDelete) r.HandleFunc("/api/v1/auth-register/host", socketHandler) @@ -232,7 +235,7 @@ func pull(w http.ResponseWriter, r *http.Request) { slog.Error("failed to get node:", "id", node.ID, "error", err) continue } - if node.FailedOverBy != uuid.Nil { + if node.FailedOverBy != uuid.Nil && r.URL.Query().Get("reset_failovered") == "true" { logic.ResetFailedOverPeer(&node) sendPeerUpdate = true } @@ -943,6 +946,7 @@ func syncHosts(w http.ResponseWriter, r *http.Request) { slog.Info("host sync requested", "user", user, "host", host.ID.String()) } }(host) + time.Sleep(time.Millisecond * 100) } }() @@ -1017,3 +1021,33 @@ func delEmqxHosts(w http.ResponseWriter, r *http.Request) { } logic.ReturnSuccessResponse(w, r, "deleted hosts data on emqx") } + +// @Summary Fetches host peerinfo +// @Router /api/host/{hostid}/peer_info [get] +// @Tags Hosts +// @Security oauth +// @Param hostid path string true "Host ID" +// @Success 200 {object} models.SuccessResponse +// @Failure 500 {object} models.ErrorResponse +func getHostPeerInfo(w http.ResponseWriter, r *http.Request) { + hostId := mux.Vars(r)["hostid"] + var errorResponse = models.ErrorResponse{} + + host, err := logic.GetHost(hostId) + if err != nil { + slog.Error("failed to retrieve host", "error", err) + errorResponse.Code = http.StatusBadRequest + errorResponse.Message = err.Error() + logic.ReturnErrorResponse(w, r, errorResponse) + return + } + peerInfo, err := logic.GetHostPeerInfo(host) + if err != nil { + slog.Error("failed to retrieve host peerinfo", "error", err) + errorResponse.Code = http.StatusBadRequest + errorResponse.Message = err.Error() + logic.ReturnErrorResponse(w, r, errorResponse) + return + } + logic.ReturnSuccessResponseWithJson(w, r, peerInfo, "fetched host peer info") +} diff --git a/logic/acls.go b/logic/acls.go index 38079bcc..613db4d6 100644 --- a/logic/acls.go +++ b/logic/acls.go @@ -164,6 +164,7 @@ func storeAclInCache(a models.Acl) { aclCacheMutex.Lock() defer aclCacheMutex.Unlock() aclCacheMap[a.ID] = a + } func removeAclFromCache(a models.Acl) { @@ -585,6 +586,7 @@ func IsPeerAllowed(node, peer models.Node, checkDefaultPolicy bool) bool { return true } } + } // list device policies policies := listDevicePolicies(models.NetworkID(peer.Network)) diff --git a/logic/extpeers.go b/logic/extpeers.go index e61d42b9..706c5631 100644 --- a/logic/extpeers.go +++ b/logic/extpeers.go @@ -461,9 +461,7 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) { defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy) nodes, _ := GetNetworkNodes(node.Network) nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...) - //fmt.Printf("=====> NODES: %+v \n\n", nodes) userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network)) - //fmt.Printf("=====> USER NODES %+v \n\n", userNodes) for _, userNodeI := range userNodes { for _, peer := range nodes { if peer.IsUserNode { diff --git a/logic/nodes.go b/logic/nodes.go index 0b0c2495..f47e6512 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -40,9 +40,7 @@ func getNodeFromCache(nodeID string) (node models.Node, ok bool) { } func getNodesFromCache() (nodes []models.Node) { nodeCacheMutex.RLock() - for _, node := range nodesCacheMap { - nodes = append(nodes, node) - } + nodes = slices.Collect(maps.Values(nodesCacheMap)) nodeCacheMutex.RUnlock() return } @@ -141,7 +139,7 @@ func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node defer nodeNetworkCacheMutex.Unlock() return slices.Collect(maps.Values(networkNodes)) } - var nodes = []models.Node{} + var nodes = make([]models.Node, 0, len(allNodes)) for i := range allNodes { node := allNodes[i] if node.Network == network { diff --git a/logic/peers.go b/logic/peers.go index 076c0dbb..64f6acbb 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -59,6 +59,80 @@ var ( } ) +// GetHostPeerInfo - fetches required peer info per network +func GetHostPeerInfo(host *models.Host) (models.HostPeerInfo, error) { + peerInfo := models.HostPeerInfo{ + NetworkPeerIDs: make(map[models.NetworkID]models.PeerMap), + } + allNodes, err := GetAllNodes() + if err != nil { + return peerInfo, err + } + for _, nodeID := range host.Nodes { + nodeID := nodeID + node, err := GetNodeByID(nodeID) + if err != nil { + continue + } + + if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE { + continue + } + networkPeersInfo := make(models.PeerMap) + defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy) + + currentPeers := GetNetworkNodesMemory(allNodes, node.Network) + for _, peer := range currentPeers { + peer := peer + if peer.ID.String() == node.ID.String() { + logger.Log(2, "peer update, skipping self") + // skip yourself + continue + } + + peerHost, err := GetHost(peer.HostID.String()) + if err != nil { + logger.Log(1, "no peer host", peer.HostID.String(), err.Error()) + continue + } + + var allowedToComm bool + if defaultDevicePolicy.Enabled { + allowedToComm = true + } else { + allowedToComm = IsPeerAllowed(node, peer, false) + } + if peer.Action != models.NODE_DELETE && + !peer.PendingDelete && + peer.Connected && + nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) && + (defaultDevicePolicy.Enabled || allowedToComm) { + + networkPeersInfo[peerHost.PublicKey.String()] = models.IDandAddr{ + ID: peer.ID.String(), + HostID: peerHost.ID.String(), + Address: peer.PrimaryAddress(), + Name: peerHost.Name, + Network: peer.Network, + ListenPort: peerHost.ListenPort, + } + + } + } + var extPeerIDAndAddrs []models.IDandAddr + if node.IsIngressGateway { + _, extPeerIDAndAddrs, _, err = GetExtPeers(&node, &node) + if err == nil { + for _, extPeerIdAndAddr := range extPeerIDAndAddrs { + networkPeersInfo[extPeerIdAndAddr.ID] = extPeerIdAndAddr + } + } + } + peerInfo.NetworkPeerIDs[models.NetworkID(node.Network)] = networkPeersInfo + } + return peerInfo, nil +} + // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.Node, deletedNode *models.Node, deletedClients []models.ExtClient) (models.HostPeerUpdate, error) { @@ -295,15 +369,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N peerConfig.Endpoint.IP = peer.LocalAddress.IP peerConfig.Endpoint.Port = peerHost.ListenPort } - allowedips := GetAllowedIPs(&node, &peer, nil) - allowedToComm := IsPeerAllowed(node, peer, false) + var allowedToComm bool + if defaultDevicePolicy.Enabled { + allowedToComm = true + } else { + allowedToComm = IsPeerAllowed(node, peer, false) + } if peer.Action != models.NODE_DELETE && !peer.PendingDelete && peer.Connected && nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) && (defaultDevicePolicy.Enabled || allowedToComm) && (deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) { - peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection + peerConfig.AllowedIPs = GetAllowedIPs(&node, &peer, nil) // only append allowed IPs if valid connection } var nodePeer wgtypes.PeerConfig diff --git a/logic/pro/failover b/logic/pro/failover deleted file mode 100644 index e69de29b..00000000 diff --git a/models/mqtt.go b/models/mqtt.go index 4b7ce10a..80c3d5b0 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -6,6 +6,10 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) +type HostPeerInfo struct { + NetworkPeerIDs map[NetworkID]PeerMap `json:"network_peers"` +} + // HostPeerUpdate - struct for host peer updates type HostPeerUpdate struct { Host Host `json:"host"` diff --git a/pro/controllers/failover.go b/pro/controllers/failover.go index a4fddad9..13a9df30 100644 --- a/pro/controllers/failover.go +++ b/pro/controllers/failover.go @@ -19,7 +19,7 @@ import ( // FailOverHandlers - handlers for FailOver func FailOverHandlers(r *mux.Router) { - r.HandleFunc("/api/v1/node/{nodeid}/failover", http.HandlerFunc(getfailOver)). + r.HandleFunc("/api/v1/node/{nodeid}/failover", controller.Authorize(true, false, "host", http.HandlerFunc(getfailOver))). Methods(http.MethodGet) r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))). Methods(http.MethodPost) @@ -29,6 +29,8 @@ func FailOverHandlers(r *mux.Router) { Methods(http.MethodPost) r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))). Methods(http.MethodPost) + r.HandleFunc("/api/v1/node/{nodeid}/failover_check", controller.Authorize(true, false, "host", http.HandlerFunc(checkfailOverCtx))). + Methods(http.MethodGet) } // @Summary Get failover node @@ -44,7 +46,6 @@ func getfailOver(w http.ResponseWriter, r *http.Request) { // confirm host exists node, err := logic.GetNodeByID(nodeid) if err != nil { - slog.Error("failed to get node:", "node", nodeid, "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } @@ -140,6 +141,7 @@ func deletefailOver(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + proLogic.RemoveFailOverFromCache(node.Network) go func() { proLogic.ResetFailOver(&node) mq.PublishPeerUpdate(false) @@ -265,10 +267,9 @@ func failOverME(w http.ResponseWriter, r *http.Request) { ) return } - err = proLogic.SetFailOverCtx(failOverNode, node, peerNode) if err != nil { - slog.Error("failed to create failover", "id", node.ID.String(), + slog.Debug("failed to create failover", "id", node.ID.String(), "network", node.Network, "error", err) logic.ReturnErrorResponse( w, @@ -293,3 +294,135 @@ func failOverME(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") logic.ReturnSuccessResponse(w, r, "relayed successfully") } + +// @Summary checkfailOverCtx +// @Router /api/v1/node/{nodeid}/failover_check [get] +// @Tags PRO +// @Param nodeid path string true "Node ID" +// @Accept json +// @Param body body models.FailOverMeReq true "Failover request" +// @Success 200 {object} models.SuccessResponse +// @Failure 400 {object} models.ErrorResponse +// @Failure 500 {object} models.ErrorResponse +func checkfailOverCtx(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + nodeid := params["nodeid"] + // confirm host exists + node, err := logic.GetNodeByID(nodeid) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to get node:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + + failOverNode, exists := proLogic.FailOverExists(node.Network) + if !exists { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError( + fmt.Errorf("req-from: %s, failover node doesn't exist in the network", host.Name), + "badrequest", + ), + ) + return + } + var failOverReq models.FailOverMeReq + err = json.NewDecoder(r.Body).Decode(&failOverReq) + if err != nil { + logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + peerNode, err := logic.GetNodeByID(failOverReq.NodeID) + if err != nil { + slog.Error("peer not found: ", "nodeid", failOverReq.NodeID, "error", err) + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("peer not found"), "badrequest"), + ) + return + } + if peerNode.IsFailOver { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("peer is acting as failover"), "badrequest"), + ) + return + } + if node.IsFailOver { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("node is acting as failover"), "badrequest"), + ) + return + } + if peerNode.IsFailOver { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("peer is acting as failover"), "badrequest"), + ) + return + } + if node.IsRelayed && node.RelayedBy == peerNode.ID.String() { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("node is relayed by peer node"), "badrequest"), + ) + return + } + if node.IsRelay && peerNode.RelayedBy == node.ID.String() { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(errors.New("node acting as relay for the peer node"), "badrequest"), + ) + return + } + if node.IsInternetGateway && peerNode.InternetGwID == node.ID.String() { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError( + errors.New("node acting as internet gw for the peer node"), + "badrequest", + ), + ) + return + } + if node.InternetGwID != "" && node.InternetGwID == peerNode.ID.String() { + logic.ReturnErrorResponse( + w, + r, + logic.FormatError( + errors.New("node using a internet gw by the peer node"), + "badrequest", + ), + ) + return + } + + err = proLogic.CheckFailOverCtx(failOverNode, node, peerNode) + if err != nil { + slog.Error("failover ctx cannot be set ", "error", err) + logic.ReturnErrorResponse( + w, + r, + logic.FormatError(fmt.Errorf("failover ctx cannot be set: %v", err), "internal"), + ) + return + } + + w.Header().Set("Content-Type", "application/json") + logic.ReturnSuccessResponse(w, r, "failover can be set") +} diff --git a/pro/initialize.go b/pro/initialize.go index 6c6587f9..4292d3f9 100644 --- a/pro/initialize.go +++ b/pro/initialize.go @@ -90,6 +90,7 @@ func InitPro() { slog.Error("no OAuth provider found or not configured, continuing without OAuth") } proLogic.LoadNodeMetricsToCache() + proLogic.InitFailOverCache() }) logic.ResetFailOver = proLogic.ResetFailOver logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer diff --git a/pro/logic/failover.go b/pro/logic/failover.go index 4b0debdc..d4ac5ff6 100644 --- a/pro/logic/failover.go +++ b/pro/logic/failover.go @@ -13,7 +13,49 @@ import ( ) var failOverCtxMutex = &sync.RWMutex{} +var failOverCacheMutex = &sync.RWMutex{} +var failOverCache = make(map[models.NetworkID]string) +func InitFailOverCache() { + failOverCacheMutex.Lock() + defer failOverCacheMutex.Unlock() + networks, err := logic.GetNetworks() + if err != nil { + return + } + allNodes, err := logic.GetAllNodes() + if err != nil { + return + } + + for _, network := range networks { + networkNodes := logic.GetNetworkNodesMemory(allNodes, network.NetID) + for _, node := range networkNodes { + if node.IsFailOver { + failOverCache[models.NetworkID(network.NetID)] = node.ID.String() + break + } + } + } +} + +func CheckFailOverCtx(failOverNode, victimNode, peerNode models.Node) error { + failOverCtxMutex.RLock() + defer failOverCtxMutex.RUnlock() + if peerNode.FailOverPeers == nil { + return nil + } + if victimNode.FailOverPeers == nil { + return nil + } + _, peerHasFailovered := peerNode.FailOverPeers[victimNode.ID.String()] + _, victimHasFailovered := victimNode.FailOverPeers[peerNode.ID.String()] + if peerHasFailovered && victimHasFailovered && + victimNode.FailedOverBy == failOverNode.ID && peerNode.FailedOverBy == failOverNode.ID { + return errors.New("failover ctx is already set") + } + return nil +} func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error { failOverCtxMutex.Lock() defer failOverCtxMutex.Unlock() @@ -23,13 +65,16 @@ func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error { if victimNode.FailOverPeers == nil { victimNode.FailOverPeers = make(map[string]struct{}) } + _, peerHasFailovered := peerNode.FailOverPeers[victimNode.ID.String()] + _, victimHasFailovered := victimNode.FailOverPeers[peerNode.ID.String()] + if peerHasFailovered && victimHasFailovered && + victimNode.FailedOverBy == failOverNode.ID && peerNode.FailedOverBy == failOverNode.ID { + return errors.New("failover ctx is already set") + } peerNode.FailOverPeers[victimNode.ID.String()] = struct{}{} victimNode.FailOverPeers[peerNode.ID.String()] = struct{}{} victimNode.FailedOverBy = failOverNode.ID peerNode.FailedOverBy = failOverNode.ID - if err := logic.UpsertNode(&failOverNode); err != nil { - return err - } if err := logic.UpsertNode(&victimNode); err != nil { return err } @@ -50,17 +95,26 @@ func GetFailOverNode(network string, allNodes []models.Node) (models.Node, error return models.Node{}, errors.New("auto relay not found") } +func RemoveFailOverFromCache(network string) { + failOverCacheMutex.Lock() + defer failOverCacheMutex.Unlock() + delete(failOverCache, models.NetworkID(network)) +} + +func SetFailOverInCache(node models.Node) { + failOverCacheMutex.Lock() + defer failOverCacheMutex.Unlock() + failOverCache[models.NetworkID(node.Network)] = node.ID.String() +} + // FailOverExists - checks if failOver exists already in the network func FailOverExists(network string) (failOverNode models.Node, exists bool) { - nodes, err := logic.GetNetworkNodes(network) - if err != nil { - return - } - for _, node := range nodes { - if node.IsFailOver { - exists = true - failOverNode = node - return + failOverCacheMutex.RLock() + defer failOverCacheMutex.RUnlock() + if nodeID, ok := failOverCache[models.NetworkID(network)]; ok { + failOverNode, err := logic.GetNodeByID(nodeID) + if err == nil { + return failOverNode, true } } return @@ -185,5 +239,6 @@ func CreateFailOver(node models.Node) error { slog.Error("failed to upsert node", "node", node.ID.String(), "error", err) return err } + SetFailOverInCache(node) return nil }