NM-214: Expect GeoInfo to come from Netclient (#3833)

* feat(go): expect geoinfo from netclient;

* feat(go): add geoinfo util;
This commit is contained in:
Vishal Dalwadi 2026-02-02 15:44:49 +05:30 committed by GitHub
parent 635d47ec3d
commit f723fc5202
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 128 additions and 71 deletions

View file

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
"os"
"slices"
"time"
@ -360,11 +359,6 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
)
return
}
if newHost.EndpointIP != nil {
newHost.Location, newHost.CountryCode = logic.GetHostLocInfo(newHost.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
} else if newHost.EndpointIPv6 != nil {
newHost.Location, newHost.CountryCode = logic.GetHostLocInfo(newHost.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
}
pcviolations := []models.Violation{}
skipViolatedNetworks := []string{}
keyTags := make(map[models.TagID]struct{})

View file

@ -7,7 +7,6 @@ import (
"fmt"
"net"
"net/http"
"os"
"reflect"
"strconv"
"strings"
@ -784,9 +783,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
}
extclient.PublicEndpoint = customExtClient.PublicEndpoint
extclient.Country = customExtClient.Country
if customExtClient.RemoteAccessClientID != "" && customExtClient.Location == "" {
extclient.Location, extclient.Country = logic.GetHostLocInfo(logic.GetClientIP(r), os.Getenv("IP_INFO_TOKEN"))
}
extclient.Location = customExtClient.Location
// JIT enforcement: Check if user has access (only for desktop app users)
hasAccess, grant, err := logic.CheckJITAccess(extclient.Network, userName)
@ -956,9 +952,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
//remove old peer entry
replacePeers = true
}
if update.RemoteAccessClientID != "" && update.Location == "" {
update.Location, update.Country = logic.GetHostLocInfo(logic.GetClientIP(r), os.Getenv("IP_INFO_TOKEN"))
}
newclient := logic.UpdateExtClient(&oldExtClient, &update)
if newclient.DeviceID != "" && newclient.Enabled {
// check for violations connecting from desktop app

View file

@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
"sync"
@ -34,8 +33,6 @@ var (
ErrInvalidHostID error = errors.New("invalid host id")
)
var GetHostLocInfo = func(ip, token string) (string, string) { return "", "" }
var CheckPostureViolations = func(d models.PostureCheckDeviceInfo, network models.NetworkID) (v []models.Violation, level models.Severity) {
return []models.Violation{}, models.SeverityUnknown
}
@ -261,11 +258,6 @@ func CreateHost(h *models.Host) error {
} else {
h.DNS = "no"
}
if h.EndpointIP != nil {
h.Location, h.CountryCode = GetHostLocInfo(h.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
} else if h.EndpointIPv6 != nil {
h.Location, h.CountryCode = GetHostLocInfo(h.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
}
if !GetFeatureFlags().EnableFlowLogs || !GetServerSettings().EnableFlowLogs {
h.EnableFlowLogs = false

View file

@ -9,7 +9,6 @@ import (
"log"
"math/big"
"net"
"os"
"time"
"golang.org/x/exp/slog"
@ -618,16 +617,6 @@ func updateHosts() {
host.AutoUpdate = true
logic.UpsertHost(&host)
}
if servercfg.IsPro && (host.Location == "" || host.CountryCode == "") {
if host.EndpointIP != nil {
host.Location, host.CountryCode = logic.GetHostLocInfo(host.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
} else if host.EndpointIPv6 != nil {
host.Location, host.CountryCode = logic.GetHostLocInfo(host.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
}
if host.Location != "" && host.CountryCode != "" {
logic.UpsertHost(&host)
}
}
}
}

View file

@ -2,7 +2,6 @@ package mq
import (
"encoding/json"
"os"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
@ -300,13 +299,6 @@ func HandleHostCheckin(h, currentHost *models.Host) bool {
(!h.EndpointIPv6.Equal(currentHost.EndpointIPv6)) || (h.OSFamily != currentHost.OSFamily) ||
(h.OSVersion != currentHost.OSVersion) || (h.KernelVersion != currentHost.KernelVersion)
if ifaceDelta { // only save if something changes
if !h.EndpointIP.Equal(currentHost.EndpointIP) || !h.EndpointIPv6.Equal(currentHost.EndpointIPv6) || currentHost.Location == "" {
if h.EndpointIP != nil {
h.Location, h.CountryCode = logic.GetHostLocInfo(h.EndpointIP.String(), os.Getenv("IP_INFO_TOKEN"))
} else if h.EndpointIPv6 != nil {
h.Location, h.CountryCode = logic.GetHostLocInfo(h.EndpointIPv6.String(), os.Getenv("IP_INFO_TOKEN"))
}
}
currentHost.EndpointIP = h.EndpointIP
currentHost.EndpointIPv6 = h.EndpointIPv6
currentHost.Interfaces = h.Interfaces

View file

@ -195,7 +195,6 @@ func InitPro() {
logic.MigrateToGws = proLogic.MigrateToGws
logic.GetFwRulesForNodeAndPeerOnGw = proLogic.GetFwRulesForNodeAndPeerOnGw
logic.GetFwRulesForUserNodesOnGw = proLogic.GetFwRulesForUserNodesOnGw
logic.GetHostLocInfo = proLogic.GetHostLocInfo
logic.GetFeatureFlags = proLogic.GetFeatureFlags
logic.GetDeploymentMode = proLogic.GetDeploymentMode
logic.GetNameserversForHost = proLogic.GetNameserversForHost

View file

@ -2,7 +2,6 @@ package logic
import (
"encoding/json"
"net/http"
"sync"
"time"
@ -239,32 +238,3 @@ 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) (loc, country 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"
Country string `json:"country"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return "", ""
}
loc = data.Loc
country = data.Country
return
}

128
utils/geoinfo.go Normal file
View file

@ -0,0 +1,128 @@
package utils
import (
"encoding/json"
"fmt"
"net/http"
)
type GeoInfo struct {
IP string
CountryCode string
Location string
}
// GetGeoInfo returns the ip, location and country code of the host it's called on.
func GetGeoInfo() (*GeoInfo, error) {
geoInfo, err := getGeoInfoFromIPAPI()
if err == nil {
return geoInfo, nil
}
geoInfo, err = getGeoInfoFromCloudFlare()
if err == nil {
return geoInfo, nil
}
return getGeoInfoFromIpInfo()
}
func getGeoInfoFromIPAPI() (*GeoInfo, error) {
resp, err := http.Get("https://api.ipapi.is")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var data struct {
IP string `json:"ip"`
Location struct {
CountryCode string `json:"country_code"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
} `json:"location"`
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("status code %d", resp.StatusCode)
}
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
return nil, err
}
return &GeoInfo{
IP: data.IP,
Location: data.Location.Latitude + "," + data.Location.Longitude,
CountryCode: data.Location.CountryCode,
}, nil
}
func getGeoInfoFromCloudFlare() (*GeoInfo, error) {
var geoInfo GeoInfo
resp, err := http.Get("https://speed.cloudflare.com/meta")
if err != nil {
return nil, err
}
defer resp.Body.Close()
respMap := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&respMap)
if err != nil {
return nil, err
}
_, ok := respMap["clientIp"]
if ok {
geoInfo.IP = respMap["clientIp"].(string)
}
_, ok = respMap["country"]
if ok {
geoInfo.CountryCode = respMap["country"].(string)
}
var latitude, longitude string
_, ok = respMap["latitude"]
if ok {
latitude = respMap["latitude"].(string)
}
_, ok = respMap["longitude"]
if ok {
longitude = respMap["longitude"].(string)
}
if latitude != "" && longitude != "" {
geoInfo.Location = latitude + "," + longitude
}
return &geoInfo, nil
}
func getGeoInfoFromIpInfo() (*GeoInfo, error) {
var geoInfo GeoInfo
resp, err := http.Get("https://ipinfo.io/json")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var data struct {
IP string `json:"ip"`
Loc string `json:"loc"`
Country string `json:"country"`
}
err = json.NewDecoder(resp.Body).Decode(&data)
if err != nil {
return nil, err
}
geoInfo.IP = data.IP
geoInfo.CountryCode = data.Country
geoInfo.Location = data.Loc
return &geoInfo, nil
}