瀏覽代碼

Merge pull request #3502 from gravitl/NET-1994

NET-1994: collect host location for graph
Abhishek K 2 月之前
父節點
當前提交
f7471dcd82
共有 15 個文件被更改,包括 131 次插入4 次删除
  1. 8 1
      controllers/ext_client.go
  2. 6 0
      logic/extpeers.go
  3. 8 0
      logic/hosts.go
  4. 18 0
      logic/nodes.go
  5. 0 2
      logic/peers.go
  6. 18 0
      logic/util.go
  7. 11 0
      migrate/migrate.go
  8. 3 0
      models/api_host.go
  9. 1 0
      models/api_node.go
  10. 2 0
      models/extclient.go
  11. 1 0
      models/host.go
  12. 8 1
      mq/handlers.go
  13. 19 0
      pro/controllers/metrics.go
  14. 1 0
      pro/initialize.go
  15. 27 0
      pro/logic/metrics.go

+ 8 - 1
controllers/ext_client.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"os"
 	"reflect"
 	"strconv"
 	"strings"
@@ -673,7 +674,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
-
 	ingressExists := checkIngressExists(nodeid)
 	if !ingressExists {
 		err := errors.New("ingress does not exist")
@@ -780,6 +780,10 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	extclient.PublicEndpoint = customExtClient.PublicEndpoint
 	extclient.Country = customExtClient.Country
+	if customExtClient.RemoteAccessClientID != "" && customExtClient.Location == "" {
+		extclient.Location = logic.GetHostLocInfo(logic.GetClientIP(r), os.Getenv("IP_INFO_TOKEN"))
+	}
+	extclient.Location = customExtClient.Location
 
 	if err = logic.CreateExtClient(&extclient); err != nil {
 		slog.Error(
@@ -928,6 +932,9 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 		sendPeerUpdate = true
 		replacePeers = true
 	}
+	if update.RemoteAccessClientID != "" && update.Location == "" {
+		update.Location = logic.GetHostLocInfo(logic.GetClientIP(r), os.Getenv("IP_INFO_TOKEN"))
+	}
 	newclient := logic.UpdateExtClient(&oldExtClient, &update)
 	if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
 		slog.Error(

+ 6 - 0
logic/extpeers.go

@@ -427,6 +427,12 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) mode
 	new.PostUp = strings.Replace(update.PostUp, "\r\n", "\n", -1)
 	new.PostDown = strings.Replace(update.PostDown, "\r\n", "\n", -1)
 	new.Tags = update.Tags
+	if update.Location != "" && update.Location != old.Location {
+		new.Location = update.Location
+	}
+	if update.Country != "" && update.Country != old.Country {
+		new.Country = update.Country
+	}
 	return new
 }
 

+ 8 - 0
logic/hosts.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"os"
 	"sort"
 	"sync"
 
@@ -30,6 +31,8 @@ var (
 	ErrInvalidHostID error = errors.New("invalid host id")
 )
 
+var GetHostLocInfo = func(ip, token string) string { return "" }
+
 func getHostsFromCache() (hosts []models.Host) {
 	hostCacheMutex.RLock()
 	for _, host := range hostsCacheMap {
@@ -235,6 +238,11 @@ func CreateHost(h *models.Host) error {
 	} else {
 		h.DNS = "no"
 	}
+	if h.EndpointIP != nil {
+		h.Location = GetHostLocInfo(h.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
+	} else if h.EndpointIPv6 != nil {
+		h.Location = GetHostLocInfo(h.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
+	}
 	checkForZombieHosts(h)
 	return UpsertHost(h)
 }

+ 18 - 0
logic/nodes.go

@@ -594,6 +594,24 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode {
 	return apiNodes[:]
 }
 
+// GetAllNodesAPI - get all nodes for api usage
+func GetAllNodesAPIWithLocation(nodes []models.Node) []models.ApiNode {
+	apiNodes := []models.ApiNode{}
+	for i := range nodes {
+		node := nodes[i]
+		newApiNode := node.ConvertToAPINode()
+		if node.IsStatic {
+			newApiNode.Location = node.StaticNode.Location
+		} else {
+			host, _ := GetHost(node.HostID.String())
+			newApiNode.Location = host.Location
+		}
+
+		apiNodes = append(apiNodes, *newApiNode)
+	}
+	return apiNodes[:]
+}
+
 // GetNodesStatusAPI - gets nodes status
 func GetNodesStatusAPI(nodes []models.Node) map[string]models.ApiNodeStatus {
 	apiStatusNodesMap := make(map[string]models.ApiNodeStatus)

+ 0 - 2
logic/peers.go

@@ -89,7 +89,6 @@ func GetHostPeerInfo(host *models.Host) (models.HostPeerInfo, error) {
 		for _, peer := range currentPeers {
 			peer := peer
 			if peer.ID.String() == node.ID.String() {
-				logger.Log(2, "peer update, skipping self")
 				// skip yourself
 				continue
 			}
@@ -244,7 +243,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		for _, peer := range currentPeers {
 			peer := peer
 			if peer.ID.String() == node.ID.String() {
-				logger.Log(2, "peer update, skipping self")
 				// skip yourself
 				continue
 			}

+ 18 - 0
logic/util.go

@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"log/slog"
 	"net"
+	"net/http"
 	"os"
 	"reflect"
 	"strings"
@@ -222,3 +223,20 @@ func CompareMaps[K comparable, V any](a, b map[K]V) bool {
 
 	return true
 }
+
+func GetClientIP(r *http.Request) string {
+	// Trust X-Forwarded-For first
+	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
+		parts := strings.Split(xff, ",")
+		return strings.TrimSpace(parts[0])
+	}
+	if xrip := r.Header.Get("X-Real-IP"); xrip != "" {
+		return xrip
+	}
+
+	ip, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		return r.RemoteAddr
+	}
+	return ip
+}

+ 11 - 0
migrate/migrate.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"log"
+	"os"
 	"time"
 
 	"golang.org/x/exp/slog"
@@ -204,6 +205,16 @@ func updateHosts() {
 			}
 			logic.UpsertHost(&host)
 		}
+		if servercfg.IsPro && host.Location == "" {
+			if host.EndpointIP != nil {
+				host.Location = logic.GetHostLocInfo(host.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
+			} else if host.EndpointIPv6 != nil {
+				host.Location = logic.GetHostLocInfo(host.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
+			}
+			if host.Location != "" {
+				logic.UpsertHost(&host)
+			}
+		}
 	}
 }
 

+ 3 - 0
models/api_host.go

@@ -32,6 +32,7 @@ type ApiHost struct {
 	PersistentKeepalive int        `json:"persistentkeepalive"   yaml:"persistentkeepalive"`
 	AutoUpdate          bool       `json:"autoupdate"              yaml:"autoupdate"`
 	DNS                 string     `json:"dns"               yaml:"dns"`
+	Location            string     `json:"location"`
 }
 
 // ApiIface - the interface struct for API usage
@@ -80,6 +81,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.PersistentKeepalive = int(h.PersistentKeepalive.Seconds())
 	a.AutoUpdate = h.AutoUpdate
 	a.DNS = h.DNS
+	a.Location = h.Location
 	return &a
 }
 
@@ -126,5 +128,6 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.PersistentKeepalive = time.Duration(a.PersistentKeepalive) * time.Second
 	h.AutoUpdate = a.AutoUpdate
 	h.DNS = strings.ToLower(a.DNS)
+	h.Location = currentHost.Location
 	return &h
 }

+ 1 - 0
models/api_node.go

@@ -62,6 +62,7 @@ type ApiNode struct {
 	IsUserNode        bool                `json:"is_user_node"`
 	StaticNode        ExtClient           `json:"static_node"`
 	Status            NodeStatus          `json:"status"`
+	Location          string              `json:"location"`
 }
 
 // ApiNode.ConvertToServerNode - converts an api node to a server node

+ 2 - 0
models/extclient.go

@@ -27,6 +27,7 @@ type ExtClient struct {
 	DeviceName             string              `json:"device_name"`
 	PublicEndpoint         string              `json:"public_endpoint"`
 	Country                string              `json:"country"`
+	Location               string              `json:"location"` //format: lat,long
 	Mutex                  *sync.Mutex         `json:"-"`
 }
 
@@ -47,6 +48,7 @@ type CustomExtClient struct {
 	IsAlreadyConnectedToInetGw bool                `json:"is_already_connected_to_inet_gw"`
 	PublicEndpoint             string              `json:"public_endpoint"`
 	Country                    string              `json:"country"`
+	Location                   string              `json:"location"` //format: lat,long
 }
 
 func (ext *ExtClient) ConvertToStaticNode() Node {

+ 1 - 0
models/host.go

@@ -73,6 +73,7 @@ type Host struct {
 	NatType             string           `json:"nat_type,omitempty"      yaml:"nat_type,omitempty"`
 	TurnEndpoint        *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
 	PersistentKeepalive time.Duration    `json:"persistentkeepalive" swaggertype:"primitive,integer" format:"int64" yaml:"persistentkeepalive"`
+	Location            string           `json:"location"` // Format: "lat,lon"
 }
 
 // FormatBool converts a boolean to a [yes|no] string

+ 8 - 1
mq/handlers.go

@@ -2,6 +2,7 @@ package mq
 
 import (
 	"encoding/json"
+	"os"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/google/uuid"
@@ -280,7 +281,13 @@ 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
-
+		if !h.EndpointIP.Equal(currentHost.EndpointIP) || !h.EndpointIPv6.Equal(currentHost.EndpointIPv6) {
+			if h.EndpointIP != nil {
+				h.Location = logic.GetHostLocInfo(h.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
+			} else if h.EndpointIPv6 != nil {
+				h.Location = logic.GetHostLocInfo(h.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
+			}
+		}
 		currentHost.EndpointIP = h.EndpointIP
 		currentHost.EndpointIPv6 = h.EndpointIPv6
 		currentHost.Interfaces = h.Interfaces

+ 19 - 0
pro/controllers/metrics.go

@@ -20,6 +20,7 @@ func MetricHandlers(r *mux.Router) {
 	r.HandleFunc("/api/metrics/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodesMetrics))).Methods(http.MethodGet)
 	r.HandleFunc("/api/metrics", logic.SecurityCheck(true, http.HandlerFunc(getAllMetrics))).Methods(http.MethodGet)
 	r.HandleFunc("/api/metrics-ext/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkExtMetrics))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/graph/{network}", logic.SecurityCheck(true, http.HandlerFunc(graph))).Methods(http.MethodGet)
 }
 
 // get the metrics of a given node
@@ -165,3 +166,21 @@ func getAllMetrics(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(networkMetrics)
 }
+
+func graph(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	var params = mux.Vars(r)
+	network := params["network"]
+	networkNodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		logger.Log(1, r.Header.Get("user"), "failed to get network nodes", err.Error())
+		return
+	}
+	networkNodes = logic.AddStaticNodestoList(networkNodes)
+	// return all the nodes in JSON/API format
+	apiNodes := logic.GetAllNodesAPIWithLocation(networkNodes[:])
+	logic.SortApiNodes(apiNodes[:])
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNodes)
+}

+ 1 - 0
pro/initialize.go

@@ -162,6 +162,7 @@ func InitPro() {
 	logic.IsNodeAllowedToCommunicate = proLogic.IsNodeAllowedToCommunicate
 	logic.GetFwRulesForNodeAndPeerOnGw = proLogic.GetFwRulesForNodeAndPeerOnGw
 	logic.GetFwRulesForUserNodesOnGw = proLogic.GetFwRulesForUserNodesOnGw
+	logic.GetHostLocInfo = proLogic.GetHostLocInfo
 
 }
 

+ 27 - 0
pro/logic/metrics.go

@@ -2,6 +2,7 @@ package logic
 
 import (
 	"encoding/json"
+	"net/http"
 	"sync"
 	"time"
 
@@ -237,3 +238,29 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) {
 
 	slog.Debug("[metrics] node metrics data", "node ID", currentNode.ID, "metrics", newMetrics)
 }
+
+func GetHostLocInfo(ip, token string) string {
+	url := "https://ipinfo.io/"
+	if ip != "" {
+		url += ip
+	}
+	url += "/json"
+	if token != "" {
+		url += "?token=" + token
+	}
+
+	client := http.Client{Timeout: 3 * time.Second}
+	resp, err := client.Get(url)
+	if err != nil {
+		return ""
+	}
+	defer resp.Body.Close()
+
+	var data struct {
+		Loc string `json:"loc"` // Format: "lat,lon"
+	}
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return ""
+	}
+	return data.Loc
+}