diff --git a/cli/cmd/ext_client/config.go b/cli/cmd/ext_client/config.go index ef8e9ea6..8b3def6d 100644 --- a/cli/cmd/ext_client/config.go +++ b/cli/cmd/ext_client/config.go @@ -17,6 +17,17 @@ var extClientConfigCmd = &cobra.Command{ }, } +var extClientHAConfigCmd = &cobra.Command{ + Use: "auto_config [NETWORK NAME]", + Args: cobra.ExactArgs(1), + Short: "Get an External Client Configuration", + Long: `Get an External Client Configuration`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(functions.GetExtClientHAConfig(args[0])) + }, +} + func init() { rootCmd.AddCommand(extClientConfigCmd) + rootCmd.AddCommand(extClientHAConfigCmd) } diff --git a/cli/functions/ext_client.go b/cli/functions/ext_client.go index 42a3b44d..085e6a4d 100644 --- a/cli/functions/ext_client.go +++ b/cli/functions/ext_client.go @@ -27,6 +27,11 @@ func GetExtClientConfig(networkName, clientID string) string { return get(fmt.Sprintf("/api/extclients/%s/%s/file", networkName, clientID)) } +// GetExtClientConfig - auto fetch a client config +func GetExtClientHAConfig(networkName string) string { + return get(fmt.Sprintf("/api/v1/client_conf/%s", networkName)) +} + // CreateExtClient - create an external client func CreateExtClient(networkName, nodeID string, extClient models.CustomExtClient) { request[any](http.MethodPost, fmt.Sprintf("/api/extclients/%s/%s", networkName, nodeID), extClient) diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 1c751cc4..9dd3a411 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -42,6 +42,7 @@ func extClientHandlers(r *mux.Router) { Methods(http.MethodDelete) r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.SecurityCheck(false, checkFreeTierLimits(limitChoiceMachines, http.HandlerFunc(createExtClient)))). Methods(http.MethodPost) + r.HandleFunc("/api/v1/client_conf/{network}", logic.SecurityCheck(false, http.HandlerFunc(getExtClientHAConf))).Methods(http.MethodGet) } func checkIngressExists(nodeID string) bool { @@ -387,6 +388,251 @@ Endpoint = %s json.NewEncoder(w).Encode(client) } +// @Summary Get an individual remote access client +// @Router /api/extclients/{network}/{clientid}/{type} [get] +// @Tags Remote Access Client +// @Security oauth2 +// @Success 200 {object} models.ExtClient +// @Failure 500 {object} models.ErrorResponse +// @Failure 403 {object} models.ErrorResponse +func getExtClientHAConf(w http.ResponseWriter, r *http.Request) { + + var params = mux.Vars(r) + networkid := params["network"] + network, err := logic.GetParentNetwork(networkid) + if err != nil { + logger.Log( + 1, + r.Header.Get("user"), + "Could not retrieve Ingress Gateway Network", + networkid, + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + // fetch client based on availability + nodes, _ := logic.GetNetworkNodes(networkid) + defaultPolicy, _ := logic.GetDefaultPolicy(models.NetworkID(networkid), models.DevicePolicy) + var targetGwID string + var connectionCnt int = -1 + for _, nodeI := range nodes { + if nodeI.IsGw { + // check health status + logic.GetNodeStatus(&nodeI, defaultPolicy.Enabled) + if nodeI.Status != models.OnlineSt { + continue + } + // Get Total connections on the gw + clients := logic.GetGwExtclients(nodeI.ID.String(), networkid) + + if connectionCnt == -1 || len(clients) < connectionCnt { + connectionCnt = len(clients) + targetGwID = nodeI.ID.String() + } + + } + } + gwnode, err := logic.GetNodeByID(targetGwID) + if err != nil { + logger.Log( + 0, + r.Header.Get("user"), + fmt.Sprintf( + "failed to get ingress gateway node [%s] info: %v", + gwnode.ID, + err, + ), + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + host, err := logic.GetHost(gwnode.HostID.String()) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to get ingress gateway host for node [%s] info: %v", gwnode.ID, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + var userName string + if r.Header.Get("ismaster") == "yes" { + userName = logic.MasterUser + } else { + caller, err := logic.GetUser(r.Header.Get("user")) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + userName = caller.UserName + } + // create client + var extclient models.ExtClient + extclient.OwnerID = userName + extclient.IngressGatewayID = targetGwID + extclient.Network = networkid + extclient.Tags = make(map[models.TagID]struct{}) + // extclient.Tags[models.TagID(fmt.Sprintf("%s.%s", extclient.Network, + // models.RemoteAccessTagName))] = struct{}{} + // set extclient dns to ingressdns if extclient dns is not explicitly set + if (extclient.DNS == "") && (gwnode.IngressDNS != "") { + extclient.DNS = gwnode.IngressDNS + } + + listenPort := logic.GetPeerListenPort(host) + extclient.IngressGatewayEndpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), listenPort) + extclient.Enabled = true + + if err = logic.CreateExtClient(&extclient); err != nil { + slog.Error( + "failed to create extclient", + "user", + r.Header.Get("user"), + "network", + networkid, + "error", + err, + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + client, err := logic.GetExtClient(extclient.ClientID, networkid) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + addrString := client.Address + if addrString != "" { + addrString += "/32" + } + if client.Address6 != "" { + if addrString != "" { + addrString += "," + } + addrString += client.Address6 + "/128" + } + + keepalive := "" + if network.DefaultKeepalive != 0 { + keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive)) + } + if gwnode.IngressPersistentKeepalive != 0 { + keepalive = "PersistentKeepalive = " + strconv.Itoa(int(gwnode.IngressPersistentKeepalive)) + } + var newAllowedIPs string + if logic.IsInternetGw(gwnode) || gwnode.InternetGwID != "" { + egressrange := "0.0.0.0/0" + if gwnode.Address6.IP != nil && client.Address6 != "" { + egressrange += "," + "::/0" + } + newAllowedIPs = egressrange + } else { + newAllowedIPs = network.AddressRange + if newAllowedIPs != "" && network.AddressRange6 != "" { + newAllowedIPs += "," + } + if network.AddressRange6 != "" { + newAllowedIPs += network.AddressRange6 + } + if egressGatewayRanges, err := logic.GetEgressRangesOnNetwork(&client); err == nil { + for _, egressGatewayRange := range egressGatewayRanges { + newAllowedIPs += "," + egressGatewayRange + } + } + } + gwendpoint := "" + if host.EndpointIP.To4() == nil { + gwendpoint = fmt.Sprintf("[%s]:%d", host.EndpointIPv6.String(), host.ListenPort) + } else { + gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort) + } + defaultDNS := "" + if client.DNS != "" { + defaultDNS = "DNS = " + client.DNS + } else if gwnode.IngressDNS != "" { + defaultDNS = "DNS = " + gwnode.IngressDNS + } + + defaultMTU := 1420 + if host.MTU != 0 { + defaultMTU = host.MTU + } + if gwnode.IngressMTU != 0 { + defaultMTU = int(gwnode.IngressMTU) + } + + postUp := strings.Builder{} + if client.PostUp != "" && params["type"] != "qr" { + for _, loc := range strings.Split(client.PostUp, "\n") { + postUp.WriteString(fmt.Sprintf("PostUp = %s\n", loc)) + } + } + + postDown := strings.Builder{} + if client.PostDown != "" && params["type"] != "qr" { + for _, loc := range strings.Split(client.PostDown, "\n") { + postDown.WriteString(fmt.Sprintf("PostDown = %s\n", loc)) + } + } + + config := fmt.Sprintf(`[Interface] +Address = %s +PrivateKey = %s +MTU = %d +%s +%s +%s + +[Peer] +PublicKey = %s +AllowedIPs = %s +Endpoint = %s +%s + +`, addrString, + client.PrivateKey, + defaultMTU, + defaultDNS, + postUp.String(), + postDown.String(), + host.PublicKey, + newAllowedIPs, + gwendpoint, + keepalive, + ) + + go func() { + if err := logic.SetClientDefaultACLs(&extclient); err != nil { + slog.Error( + "failed to set default acls for extclient", + "user", + r.Header.Get("user"), + "network", + networkid, + "error", + err, + ) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + if err := mq.PublishPeerUpdate(false); err != nil { + logger.Log(1, "error publishing peer update ", err.Error()) + } + if servercfg.IsDNSMode() { + logic.SetDNS() + } + }() + + name := client.ClientID + ".conf" + w.Header().Set("Content-Type", "application/config") + w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") + w.WriteHeader(http.StatusOK) + _, err = fmt.Fprint(w, config) + if err != nil { + logger.Log(1, r.Header.Get("user"), "response writer error (file) ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + } +} + // @Summary Create an individual remote access client // @Router /api/extclients/{network}/{nodeid} [post] // @Tags Remote Access Client diff --git a/controllers/network.go b/controllers/network.go index c4d6c4d7..695d5b72 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -559,7 +559,7 @@ func createNetwork(w http.ResponseWriter, r *http.Request) { logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(network.NetID)) logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID)) logic.CreateDefaultTags(models.NetworkID(network.NetID)) - //add new network to allocated ip map + go logic.AddNetworkToAllocatedIpMap(network.NetID) go func() { @@ -640,6 +640,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { return } netNew := netOld + netNew.NameServers = payload.NameServers netNew.DefaultACL = payload.DefaultACL _, _, _, err = logic.UpdateNetwork(&netOld, &netNew) if err != nil { @@ -647,7 +648,7 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - + go mq.PublishPeerUpdate(false) slog.Info("updated network", "network", payload.NetID, "user", r.Header.Get("user")) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(payload) diff --git a/controllers/node_test.go b/controllers/node_test.go index afc9b65f..02495fd2 100644 --- a/controllers/node_test.go +++ b/controllers/node_test.go @@ -21,6 +21,10 @@ var linuxHost models.Host func TestCreateEgressGateway(t *testing.T) { var gateway models.EgressGatewayRequest gateway.Ranges = []string{"10.100.100.0/24"} + gateway.RangesWithMetric = append(gateway.RangesWithMetric, models.EgressRangeMetric{ + Network: "10.100.100.0/24", + RouteMetric: 256, + }) gateway.NetID = "skynet" deleteAllNetworks() createNet() diff --git a/logic/acls.go b/logic/acls.go index f7dcf74e..23334987 100644 --- a/logic/acls.go +++ b/logic/acls.go @@ -17,7 +17,6 @@ import ( var ( aclCacheMutex = &sync.RWMutex{} aclCacheMap = make(map[string]models.Acl) - aclTagsMutex = &sync.RWMutex{} ) func MigrateAclPolicies() { @@ -620,10 +619,22 @@ func IsPeerAllowed(node, peer models.Node, checkDefaultPolicy bool) bool { } else { peerId = peer.ID.String() } - aclTagsMutex.RLock() - peerTags := maps.Clone(peer.Tags) - nodeTags := maps.Clone(node.Tags) - aclTagsMutex.RUnlock() + + var nodeTags, peerTags map[models.TagID]struct{} + if node.Mutex != nil { + node.Mutex.Lock() + nodeTags = maps.Clone(node.Tags) + node.Mutex.Unlock() + } else { + nodeTags = node.Tags + } + if peer.Mutex != nil { + peer.Mutex.Lock() + peerTags = maps.Clone(peer.Tags) + peer.Mutex.Unlock() + } else { + peerTags = peer.Tags + } nodeTags[models.TagID(nodeId)] = struct{}{} peerTags[models.TagID(peerId)] = struct{}{} if checkDefaultPolicy { @@ -854,10 +865,21 @@ func IsNodeAllowedToCommunicateV1(node, peer models.Node, checkDefaultPolicy boo peerId = peer.ID.String() } - aclTagsMutex.RLock() - peerTags := maps.Clone(peer.Tags) - nodeTags := maps.Clone(node.Tags) - aclTagsMutex.RUnlock() + var nodeTags, peerTags map[models.TagID]struct{} + if node.Mutex != nil { + node.Mutex.Lock() + nodeTags = maps.Clone(node.Tags) + node.Mutex.Unlock() + } else { + nodeTags = node.Tags + } + if peer.Mutex != nil { + peer.Mutex.Lock() + peerTags = maps.Clone(peer.Tags) + peer.Mutex.Unlock() + } else { + peerTags = peer.Tags + } nodeTags[models.TagID(nodeId)] = struct{}{} peerTags[models.TagID(peerId)] = struct{}{} if checkDefaultPolicy { @@ -996,10 +1018,21 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool) peerId = peer.ID.String() } - aclTagsMutex.RLock() - peerTags := maps.Clone(peer.Tags) - nodeTags := maps.Clone(node.Tags) - aclTagsMutex.RUnlock() + var nodeTags, peerTags map[models.TagID]struct{} + if node.Mutex != nil { + node.Mutex.Lock() + nodeTags = maps.Clone(node.Tags) + node.Mutex.Unlock() + } else { + nodeTags = node.Tags + } + if peer.Mutex != nil { + peer.Mutex.Lock() + peerTags = maps.Clone(peer.Tags) + peer.Mutex.Unlock() + } else { + peerTags = peer.Tags + } nodeTags[models.TagID(nodeId)] = struct{}{} peerTags[models.TagID(peerId)] = struct{}{} if checkDefaultPolicy { @@ -1222,13 +1255,20 @@ func getUserAclRulesForNode(targetnode *models.Node, userGrpMap := GetUserGrpMap() allowedUsers := make(map[string][]models.Acl) acls := listUserPolicies(models.NetworkID(targetnode.Network)) - + var targetNodeTags = make(map[models.TagID]struct{}) + if targetnode.Mutex != nil { + targetnode.Mutex.Lock() + targetNodeTags = maps.Clone(targetnode.Tags) + targetnode.Mutex.Unlock() + } else { + targetNodeTags = maps.Clone(targetnode.Tags) + } for _, acl := range acls { if !acl.Enabled { continue } dstTags := convAclTagToValueMap(acl.Dst) - for nodeTag := range targetnode.Tags { + for nodeTag := range targetNodeTags { if _, ok := dstTags[nodeTag.String()]; !ok { if _, ok = dstTags[targetnode.ID.String()]; !ok { continue @@ -1338,7 +1378,15 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu } acls := listDevicePolicies(models.NetworkID(targetnode.Network)) - targetnode.Tags["*"] = struct{}{} + var targetNodeTags = make(map[models.TagID]struct{}) + if targetnode.Mutex != nil { + targetnode.Mutex.Lock() + targetNodeTags = maps.Clone(targetnode.Tags) + targetnode.Mutex.Unlock() + } else { + targetNodeTags = maps.Clone(targetnode.Tags) + } + targetNodeTags["*"] = struct{}{} for _, acl := range acls { if !acl.Enabled { @@ -1355,7 +1403,7 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu Direction: acl.AllowedDirection, Allowed: true, } - for nodeTag := range targetnode.Tags { + for nodeTag := range targetNodeTags { if acl.AllowedDirection == models.TrafficDirectionBi { var existsInSrcTag bool var existsInDstTag bool diff --git a/logic/extpeers.go b/logic/extpeers.go index 1656fddb..df8ebd48 100644 --- a/logic/extpeers.go +++ b/logic/extpeers.go @@ -28,6 +28,9 @@ var ( func getAllExtClientsFromCache() (extClients []models.ExtClient) { extClientCacheMutex.RLock() for _, extclient := range extClientCacheMap { + if extclient.Mutex == nil { + extclient.Mutex = &sync.Mutex{} + } extClients = append(extClients, extclient) } extClientCacheMutex.RUnlock() @@ -43,12 +46,18 @@ func deleteExtClientFromCache(key string) { func getExtClientFromCache(key string) (extclient models.ExtClient, ok bool) { extClientCacheMutex.RLock() extclient, ok = extClientCacheMap[key] + if extclient.Mutex == nil { + extclient.Mutex = &sync.Mutex{} + } extClientCacheMutex.RUnlock() return } func storeExtClientInCache(key string, extclient models.ExtClient) { extClientCacheMutex.Lock() + if extclient.Mutex == nil { + extclient.Mutex = &sync.Mutex{} + } extClientCacheMap[key] = extclient extClientCacheMutex.Unlock() } @@ -96,14 +105,14 @@ func DeleteExtClient(network string, clientid string) error { if err != nil { return err } - //recycle ip address - if extClient.Address != "" { - RemoveIpFromAllocatedIpMap(network, extClient.Address) - } - if extClient.Address6 != "" { - RemoveIpFromAllocatedIpMap(network, extClient.Address6) - } if servercfg.CacheEnabled() { + // recycle ip address + if extClient.Address != "" { + RemoveIpFromAllocatedIpMap(network, extClient.Address) + } + if extClient.Address6 != "" { + RemoveIpFromAllocatedIpMap(network, extClient.Address6) + } deleteExtClientFromCache(key) } go RemoveNodeFromAclPolicy(extClient.ConvertToStaticNode()) @@ -334,15 +343,16 @@ func SaveExtClient(extclient *models.ExtClient) error { } if servercfg.CacheEnabled() { storeExtClientInCache(key, *extclient) - } - if _, ok := allocatedIpMap[extclient.Network]; ok { - if extclient.Address != "" { - AddIpToAllocatedIpMap(extclient.Network, net.ParseIP(extclient.Address)) - } - if extclient.Address6 != "" { - AddIpToAllocatedIpMap(extclient.Network, net.ParseIP(extclient.Address6)) + if _, ok := allocatedIpMap[extclient.Network]; ok { + if extclient.Address != "" { + AddIpToAllocatedIpMap(extclient.Network, net.ParseIP(extclient.Address)) + } + if extclient.Address6 != "" { + AddIpToAllocatedIpMap(extclient.Network, net.ParseIP(extclient.Address6)) + } } } + return SetNetworkNodesLastModified(extclient.Network) } diff --git a/logic/gateway.go b/logic/gateway.go index 3b96fc39..46e7b588 100644 --- a/logic/gateway.go +++ b/logic/gateway.go @@ -3,6 +3,8 @@ package logic import ( "errors" "fmt" + "slices" + "sort" "time" "github.com/gravitl/netmaker/database" @@ -77,6 +79,14 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro if host.FirewallInUse == models.FIREWALL_NONE { return models.Node{}, errors.New("please install iptables or nftables on the device") } + if len(gateway.RangesWithMetric) == 0 && len(gateway.Ranges) > 0 { + for _, rangeI := range gateway.Ranges { + gateway.RangesWithMetric = append(gateway.RangesWithMetric, models.EgressRangeMetric{ + Network: rangeI, + RouteMetric: 256, + }) + } + } for i := len(gateway.Ranges) - 1; i >= 0; i-- { // check if internet gateway IPv4 if gateway.Ranges[i] == "0.0.0.0/0" || gateway.Ranges[i] == "::/0" { @@ -91,6 +101,28 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro gateway.Ranges[i] = normalized } + rangesWithMetric := []string{} + for i := len(gateway.RangesWithMetric) - 1; i >= 0; i-- { + if gateway.RangesWithMetric[i].Network == "0.0.0.0/0" || gateway.RangesWithMetric[i].Network == "::/0" { + // remove inet range + gateway.RangesWithMetric = append(gateway.RangesWithMetric[:i], gateway.RangesWithMetric[i+1:]...) + continue + } + normalized, err := NormalizeCIDR(gateway.RangesWithMetric[i].Network) + if err != nil { + return models.Node{}, err + } + gateway.RangesWithMetric[i].Network = normalized + rangesWithMetric = append(rangesWithMetric, gateway.RangesWithMetric[i].Network) + if gateway.RangesWithMetric[i].RouteMetric <= 0 || gateway.RangesWithMetric[i].RouteMetric > 999 { + gateway.RangesWithMetric[i].RouteMetric = 256 + } + } + sort.Strings(gateway.Ranges) + sort.Strings(rangesWithMetric) + if !slices.Equal(gateway.Ranges, rangesWithMetric) { + return models.Node{}, errors.New("invalid ranges") + } if gateway.NatEnabled == "" { gateway.NatEnabled = "yes" } @@ -104,6 +136,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro node.IsEgressGateway = true node.EgressGatewayRanges = gateway.Ranges node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled) + node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway node.SetLastModified() if err = UpsertNode(&node); err != nil { diff --git a/logic/networks.go b/logic/networks.go index 04b6b573..4e55946a 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -30,6 +30,9 @@ var ( // SetAllocatedIpMap - set allocated ip map for networks func SetAllocatedIpMap() error { + if !servercfg.CacheEnabled() { + return nil + } logger.Log(0, "start setting up allocated ip map") if allocatedIpMap == nil { allocatedIpMap = map[string]map[string]net.IP{} @@ -84,16 +87,25 @@ func SetAllocatedIpMap() error { // ClearAllocatedIpMap - set allocatedIpMap to nil func ClearAllocatedIpMap() { + if !servercfg.CacheEnabled() { + return + } allocatedIpMap = nil } func AddIpToAllocatedIpMap(networkName string, ip net.IP) { + if !servercfg.CacheEnabled() { + return + } networkCacheMutex.Lock() allocatedIpMap[networkName][ip.String()] = ip networkCacheMutex.Unlock() } func RemoveIpFromAllocatedIpMap(networkName string, ip string) { + if !servercfg.CacheEnabled() { + return + } networkCacheMutex.Lock() delete(allocatedIpMap[networkName], ip) networkCacheMutex.Unlock() @@ -101,6 +113,10 @@ func RemoveIpFromAllocatedIpMap(networkName string, ip string) { // AddNetworkToAllocatedIpMap - add network to allocated ip map when network is added func AddNetworkToAllocatedIpMap(networkName string) { + //add new network to allocated ip map + if !servercfg.CacheEnabled() { + return + } networkCacheMutex.Lock() allocatedIpMap[networkName] = make(map[string]net.IP) networkCacheMutex.Unlock() @@ -108,6 +124,9 @@ func AddNetworkToAllocatedIpMap(networkName string) { // RemoveNetworkFromAllocatedIpMap - remove network from allocated ip map when network is deleted func RemoveNetworkFromAllocatedIpMap(networkName string) { + if !servercfg.CacheEnabled() { + return + } networkCacheMutex.Lock() delete(allocatedIpMap, networkName) networkCacheMutex.Unlock() @@ -354,7 +373,7 @@ func GetNetworkSettings(networkname string) (models.Network, error) { } // UniqueAddress - get a unique ipv4 address -func UniqueAddress(networkName string, reverse bool) (net.IP, error) { +func UniqueAddressCache(networkName string, reverse bool) (net.IP, error) { add := net.IP{} var network models.Network network, err := GetParentNetwork(networkName) @@ -396,6 +415,49 @@ func UniqueAddress(networkName string, reverse bool) (net.IP, error) { return add, errors.New("ERROR: No unique addresses available. Check network subnet") } +// UniqueAddress - get a unique ipv4 address +func UniqueAddressDB(networkName string, reverse bool) (net.IP, error) { + add := net.IP{} + var network models.Network + network, err := GetParentNetwork(networkName) + if err != nil { + logger.Log(0, "UniqueAddressServer encountered an error") + return add, err + } + + if network.IsIPv4 == "no" { + return add, fmt.Errorf("IPv4 not active on network " + networkName) + } + //ensure AddressRange is valid + if _, _, err := net.ParseCIDR(network.AddressRange); err != nil { + logger.Log(0, "UniqueAddress encountered an error") + return add, err + } + net4 := iplib.Net4FromStr(network.AddressRange) + newAddrs := net4.FirstAddress() + + if reverse { + newAddrs = net4.LastAddress() + } + + for { + if IsIPUnique(networkName, newAddrs.String(), database.NODES_TABLE_NAME, false) && + IsIPUnique(networkName, newAddrs.String(), database.EXT_CLIENT_TABLE_NAME, false) { + return newAddrs, nil + } + if reverse { + newAddrs, err = net4.PreviousIP(newAddrs) + } else { + newAddrs, err = net4.NextIP(newAddrs) + } + if err != nil { + break + } + } + + return add, errors.New("ERROR: No unique addresses available. Check network subnet") +} + // IsIPUnique - checks if an IP is unique func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool { @@ -439,9 +501,67 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool { return isunique } +func UniqueAddress(networkName string, reverse bool) (net.IP, error) { + if servercfg.CacheEnabled() { + return UniqueAddressCache(networkName, reverse) + } + return UniqueAddressDB(networkName, reverse) +} -// UniqueAddress6 - see if ipv6 address is unique func UniqueAddress6(networkName string, reverse bool) (net.IP, error) { + if servercfg.CacheEnabled() { + return UniqueAddress6Cache(networkName, reverse) + } + return UniqueAddress6DB(networkName, reverse) +} + +// UniqueAddress6DB - see if ipv6 address is unique +func UniqueAddress6DB(networkName string, reverse bool) (net.IP, error) { + add := net.IP{} + var network models.Network + network, err := GetParentNetwork(networkName) + if err != nil { + fmt.Println("Network Not Found") + return add, err + } + if network.IsIPv6 == "no" { + return add, fmt.Errorf("IPv6 not active on network " + networkName) + } + + //ensure AddressRange is valid + if _, _, err := net.ParseCIDR(network.AddressRange6); err != nil { + return add, err + } + net6 := iplib.Net6FromStr(network.AddressRange6) + + newAddrs, err := net6.NextIP(net6.FirstAddress()) + if reverse { + newAddrs, err = net6.PreviousIP(net6.LastAddress()) + } + if err != nil { + return add, err + } + + for { + if IsIPUnique(networkName, newAddrs.String(), database.NODES_TABLE_NAME, true) && + IsIPUnique(networkName, newAddrs.String(), database.EXT_CLIENT_TABLE_NAME, true) { + return newAddrs, nil + } + if reverse { + newAddrs, err = net6.PreviousIP(newAddrs) + } else { + newAddrs, err = net6.NextIP(newAddrs) + } + if err != nil { + break + } + } + + return add, errors.New("ERROR: No unique IPv6 addresses available. Check network subnet") +} + +// UniqueAddress6Cache - see if ipv6 address is unique using cache +func UniqueAddress6Cache(networkName string, reverse bool) (net.IP, error) { add := net.IP{} var network models.Network network, err := GetParentNetwork(networkName) diff --git a/logic/nodes.go b/logic/nodes.go index bbbc9ee7..09ccfc89 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -35,12 +35,20 @@ var ( func getNodeFromCache(nodeID string) (node models.Node, ok bool) { nodeCacheMutex.RLock() node, ok = nodesCacheMap[nodeID] + if node.Mutex == nil { + node.Mutex = &sync.Mutex{} + } nodeCacheMutex.RUnlock() return } func getNodesFromCache() (nodes []models.Node) { nodeCacheMutex.RLock() - nodes = slices.Collect(maps.Values(nodesCacheMap)) + for _, node := range nodesCacheMap { + if node.Mutex == nil { + node.Mutex = &sync.Mutex{} + } + nodes = append(nodes, node) + } nodeCacheMutex.RUnlock() return } @@ -357,12 +365,15 @@ func DeleteNodeByID(node *models.Node) error { logger.Log(1, "unable to remove metrics from DB for node", node.ID.String(), err.Error()) } //recycle ip address - if node.Address.IP != nil { - RemoveIpFromAllocatedIpMap(node.Network, node.Address.IP.String()) - } - if node.Address6.IP != nil { - RemoveIpFromAllocatedIpMap(node.Network, node.Address6.IP.String()) + if servercfg.CacheEnabled() { + if node.Address.IP != nil { + RemoveIpFromAllocatedIpMap(node.Network, node.Address.IP.String()) + } + if node.Address6.IP != nil { + RemoveIpFromAllocatedIpMap(node.Network, node.Address6.IP.String()) + } } + return nil } @@ -424,6 +435,9 @@ func GetAllNodes() ([]models.Node, error) { } // add node to our array nodes = append(nodes, node) + if node.Mutex == nil { + node.Mutex = &sync.Mutex{} + } nodesMap[node.ID.String()] = node } @@ -699,15 +713,16 @@ func createNode(node *models.Node) error { if servercfg.CacheEnabled() { storeNodeInCache(*node) storeNodeInNetworkCache(*node, node.Network) - } - if _, ok := allocatedIpMap[node.Network]; ok { - if node.Address.IP != nil { - AddIpToAllocatedIpMap(node.Network, node.Address.IP) - } - if node.Address6.IP != nil { - AddIpToAllocatedIpMap(node.Network, node.Address6.IP) + if _, ok := allocatedIpMap[node.Network]; ok { + if node.Address.IP != nil { + AddIpToAllocatedIpMap(node.Network, node.Address.IP) + } + if node.Address6.IP != nil { + AddIpToAllocatedIpMap(node.Network, node.Address6.IP) + } } } + _, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), defaultACLVal) if err != nil { logger.Log(1, "failed to create node ACL for node,", node.ID.String(), "err:", err.Error()) @@ -753,16 +768,14 @@ func ValidateParams(nodeid, netid string) (models.Node, error) { func ValidateNodeIp(currentNode *models.Node, newNode *models.ApiNode) error { if currentNode.Address.IP != nil && currentNode.Address.String() != newNode.Address { - newIp, _, _ := net.ParseCIDR(newNode.Address) - ipAllocated := allocatedIpMap[currentNode.Network] - if _, ok := ipAllocated[newIp.String()]; ok { + if !IsIPUnique(newNode.Network, newNode.Address, database.NODES_TABLE_NAME, false) || + !IsIPUnique(newNode.Network, newNode.Address, database.EXT_CLIENT_TABLE_NAME, false) { return errors.New("ip specified is already allocated: " + newNode.Address) } } if currentNode.Address6.IP != nil && currentNode.Address6.String() != newNode.Address6 { - newIp, _, _ := net.ParseCIDR(newNode.Address6) - ipAllocated := allocatedIpMap[currentNode.Network] - if _, ok := ipAllocated[newIp.String()]; ok { + if !IsIPUnique(newNode.Network, newNode.Address6, database.NODES_TABLE_NAME, false) || + !IsIPUnique(newNode.Network, newNode.Address6, database.EXT_CLIENT_TABLE_NAME, false) { return errors.New("ip specified is already allocated: " + newNode.Address6) } } @@ -825,9 +838,16 @@ func GetTagMapWithNodes() (tagNodesMap map[models.TagID][]models.Node) { if nodeI.Tags == nil { continue } + if nodeI.Mutex != nil { + nodeI.Mutex.Lock() + } for nodeTagID := range nodeI.Tags { tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI) } + if nodeI.Mutex != nil { + nodeI.Mutex.Unlock() + } + } return } @@ -842,9 +862,15 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) ( if nodeI.Tags == nil { continue } + if nodeI.Mutex != nil { + nodeI.Mutex.Lock() + } for nodeTagID := range nodeI.Tags { tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI) } + if nodeI.Mutex != nil { + nodeI.Mutex.Unlock() + } } tagNodesMap["*"] = nodes if !withStaticNodes { @@ -873,17 +899,16 @@ func AddTagMapWithStaticNodes(netID models.NetworkID, continue } - for tagID := range extclient.Tags { - tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{ - IsStatic: true, - StaticNode: extclient, - }) - tagNodesMap["*"] = append(tagNodesMap["*"], models.Node{ - IsStatic: true, - StaticNode: extclient, - }) + if extclient.Mutex != nil { + extclient.Mutex.Lock() + } + for tagID := range extclient.Tags { + tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode()) + tagNodesMap["*"] = append(tagNodesMap["*"], extclient.ConvertToStaticNode()) + } + if extclient.Mutex != nil { + extclient.Mutex.Unlock() } - } return tagNodesMap } @@ -904,11 +929,14 @@ func AddTagMapWithStaticNodesWithUsers(netID models.NetworkID, if extclient.Tags == nil { continue } + if extclient.Mutex != nil { + extclient.Mutex.Lock() + } for tagID := range extclient.Tags { - tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{ - IsStatic: true, - StaticNode: extclient, - }) + tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode()) + } + if extclient.Mutex != nil { + extclient.Mutex.Unlock() } } @@ -926,9 +954,15 @@ func GetNodesWithTag(tagID models.TagID) map[string]models.Node { if nodeI.Tags == nil { continue } + if nodeI.Mutex != nil { + nodeI.Mutex.Lock() + } if _, ok := nodeI.Tags[tagID]; ok { nMap[nodeI.ID.String()] = nodeI } + if nodeI.Mutex != nil { + nodeI.Mutex.Unlock() + } } return AddStaticNodesWithTag(tag, nMap) } @@ -942,13 +976,15 @@ func AddStaticNodesWithTag(tag models.Tag, nMap map[string]models.Node) map[stri if extclient.RemoteAccessClientID != "" { continue } - if _, ok := extclient.Tags[tag.ID]; ok { - nMap[extclient.ClientID] = models.Node{ - IsStatic: true, - StaticNode: extclient, - } + if extclient.Mutex != nil { + extclient.Mutex.Lock() + } + if _, ok := extclient.Tags[tag.ID]; ok { + nMap[extclient.ClientID] = extclient.ConvertToStaticNode() + } + if extclient.Mutex != nil { + extclient.Mutex.Unlock() } - } return nMap } @@ -964,10 +1000,7 @@ func GetStaticNodeWithTag(tagID models.TagID) map[string]models.Node { return nMap } for _, extclient := range extclients { - nMap[extclient.ClientID] = models.Node{ - IsStatic: true, - StaticNode: extclient, - } + nMap[extclient.ClientID] = extclient.ConvertToStaticNode() } return nMap } diff --git a/logic/peers.go b/logic/peers.go index 7474d0ed..17f0e1ec 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -269,7 +269,11 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N } } } - + networkSettings, err := GetNetwork(node.Network) + if err != nil { + continue + } + hostPeerUpdate.NameServers = append(hostPeerUpdate.NameServers, networkSettings.NameServers...) currentPeers := GetNetworkNodesMemory(allNodes, node.Network) for _, peer := range currentPeers { peer := peer @@ -291,11 +295,12 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N } if peer.IsEgressGateway { hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, models.EgressNetworkRoutes{ - EgressGwAddr: peer.Address, - EgressGwAddr6: peer.Address6, - NodeAddr: node.Address, - NodeAddr6: node.Address6, - EgressRanges: peer.EgressGatewayRanges, + EgressGwAddr: peer.Address, + EgressGwAddr6: peer.Address6, + NodeAddr: node.Address, + NodeAddr6: node.Address6, + EgressRanges: peer.EgressGatewayRanges, + EgressRangesWithMetric: peer.EgressGatewayRequest.RangesWithMetric, }) } if peer.IsIngressGateway { diff --git a/migrate/migrate.go b/migrate/migrate.go index 24445f25..0ee70a6b 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -225,6 +225,16 @@ func updateNodes() { node.EgressGatewayRanges = egressRanges logic.UpsertNode(&node) } + if len(node.EgressGatewayRequest.Ranges) > 0 && len(node.EgressGatewayRequest.RangesWithMetric) == 0 { + for _, egressRangeI := range node.EgressGatewayRequest.Ranges { + node.EgressGatewayRequest.RangesWithMetric = append(node.EgressGatewayRequest.RangesWithMetric, models.EgressRangeMetric{ + Network: egressRangeI, + RouteMetric: 256, + }) + } + logic.UpsertNode(&node) + } + } } } diff --git a/models/api_node.go b/models/api_node.go index 99c752e5..4cf0af3c 100644 --- a/models/api_node.go +++ b/models/api_node.go @@ -17,35 +17,36 @@ type ApiNodeStatus struct { // ApiNode is a stripped down Node DTO that exposes only required fields to external systems type ApiNode struct { - ID string `json:"id,omitempty" validate:"required,min=5,id_unique"` - HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"` - Address string `json:"address" validate:"omitempty,cidrv4"` - Address6 string `json:"address6" validate:"omitempty,cidrv6"` - LocalAddress string `json:"localaddress" validate:"omitempty,cidr"` - AllowedIPs []string `json:"allowedips"` - LastModified int64 `json:"lastmodified" swaggertype:"primitive,integer" format:"int64"` - ExpirationDateTime int64 `json:"expdatetime" swaggertype:"primitive,integer" format:"int64"` - LastCheckIn int64 `json:"lastcheckin" swaggertype:"primitive,integer" format:"int64"` - LastPeerUpdate int64 `json:"lastpeerupdate" swaggertype:"primitive,integer" format:"int64"` - Network string `json:"network"` - NetworkRange string `json:"networkrange"` - NetworkRange6 string `json:"networkrange6"` - IsRelayed bool `json:"isrelayed"` - IsRelay bool `json:"isrelay"` - RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"` - RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"` - IsEgressGateway bool `json:"isegressgateway"` - IsIngressGateway bool `json:"isingressgateway"` - EgressGatewayRanges []string `json:"egressgatewayranges"` - EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"` - DNSOn bool `json:"dnson"` - IngressDns string `json:"ingressdns"` - IngressPersistentKeepalive int32 `json:"ingresspersistentkeepalive"` - IngressMTU int32 `json:"ingressmtu"` - Server string `json:"server"` - Connected bool `json:"connected"` - PendingDelete bool `json:"pendingdelete"` - Metadata string `json:"metadata"` + ID string `json:"id,omitempty" validate:"required,min=5,id_unique"` + HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"` + Address string `json:"address" validate:"omitempty,cidrv4"` + Address6 string `json:"address6" validate:"omitempty,cidrv6"` + LocalAddress string `json:"localaddress" validate:"omitempty,cidr"` + AllowedIPs []string `json:"allowedips"` + LastModified int64 `json:"lastmodified" swaggertype:"primitive,integer" format:"int64"` + ExpirationDateTime int64 `json:"expdatetime" swaggertype:"primitive,integer" format:"int64"` + LastCheckIn int64 `json:"lastcheckin" swaggertype:"primitive,integer" format:"int64"` + LastPeerUpdate int64 `json:"lastpeerupdate" swaggertype:"primitive,integer" format:"int64"` + Network string `json:"network"` + NetworkRange string `json:"networkrange"` + NetworkRange6 string `json:"networkrange6"` + IsRelayed bool `json:"isrelayed"` + IsRelay bool `json:"isrelay"` + RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"` + RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"` + IsEgressGateway bool `json:"isegressgateway"` + IsIngressGateway bool `json:"isingressgateway"` + EgressGatewayRanges []string `json:"egressgatewayranges"` + EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"` + EgressGatewayRangesWithMetric []EgressRangeMetric `json:"egressgatewayranges_with_metric"` + DNSOn bool `json:"dnson"` + IngressDns string `json:"ingressdns"` + IngressPersistentKeepalive int32 `json:"ingresspersistentkeepalive"` + IngressMTU int32 `json:"ingressmtu"` + Server string `json:"server"` + Connected bool `json:"connected"` + PendingDelete bool `json:"pendingdelete"` + Metadata string `json:"metadata"` // == PRO == DefaultACL string `json:"defaultacl,omitempty" validate:"checkyesornoorunset"` IsFailOver bool `json:"is_fail_over"` @@ -189,6 +190,7 @@ func (nm *Node) ConvertToAPINode() *ApiNode { apiNode.IsEgressGateway = nm.IsEgressGateway apiNode.IsIngressGateway = nm.IsIngressGateway apiNode.EgressGatewayRanges = nm.EgressGatewayRanges + apiNode.EgressGatewayRangesWithMetric = nm.EgressGatewayRequest.RangesWithMetric apiNode.EgressGatewayNatEnabled = nm.EgressGatewayNatEnabled apiNode.DNSOn = nm.DNSOn apiNode.IngressDns = nm.IngressDNS diff --git a/models/extclient.go b/models/extclient.go index 51bed63a..f7762a2e 100644 --- a/models/extclient.go +++ b/models/extclient.go @@ -1,5 +1,7 @@ package models +import "sync" + // ExtClient - struct for external clients type ExtClient struct { ClientID string `json:"clientid" bson:"clientid"` @@ -25,6 +27,7 @@ type ExtClient struct { DeviceName string `json:"device_name"` PublicEndpoint string `json:"public_endpoint"` Country string `json:"country"` + Mutex *sync.Mutex `json:"-"` } // CustomExtClient - struct for CustomExtClient params @@ -57,5 +60,6 @@ func (ext *ExtClient) ConvertToStaticNode() Node { Tags: ext.Tags, IsStatic: true, StaticNode: *ext, + Mutex: ext.Mutex, } } diff --git a/models/mqtt.go b/models/mqtt.go index 80c3d5b0..2db7c51b 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -27,6 +27,7 @@ type HostPeerUpdate struct { EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"` FwUpdate FwUpdate `json:"fw_update"` ReplacePeers bool `json:"replace_peers"` + NameServers []string `json:"name_servers"` ServerConfig OldPeerUpdateFields } @@ -69,11 +70,12 @@ type EgressInfo struct { // EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface type EgressNetworkRoutes struct { - EgressGwAddr net.IPNet `json:"egress_gw_addr" yaml:"egress_gw_addr"` - EgressGwAddr6 net.IPNet `json:"egress_gw_addr6" yaml:"egress_gw_addr6"` - NodeAddr net.IPNet `json:"node_addr"` - NodeAddr6 net.IPNet `json:"node_addr6"` - EgressRanges []string `json:"egress_ranges"` + EgressGwAddr net.IPNet `json:"egress_gw_addr" yaml:"egress_gw_addr"` + EgressGwAddr6 net.IPNet `json:"egress_gw_addr6" yaml:"egress_gw_addr6"` + NodeAddr net.IPNet `json:"node_addr"` + NodeAddr6 net.IPNet `json:"node_addr6"` + EgressRanges []string `json:"egress_ranges"` + EgressRangesWithMetric []EgressRangeMetric `json:"egress_ranges_metric"` } // PeerRouteInfo - struct for peer info for an ext. client diff --git a/models/network.go b/models/network.go index 32707933..7ea41cce 100644 --- a/models/network.go +++ b/models/network.go @@ -8,22 +8,23 @@ import ( // Network Struct - contains info for a given unique network // At some point, need to replace all instances of Name with something else like Identifier type Network struct { - AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"` - AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"` - NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"` - NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified" swaggertype:"primitive,integer" format:"int64"` - NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified" swaggertype:"primitive,integer" format:"int64"` - DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"` - DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"` - NodeLimit int32 `json:"nodelimit" bson:"nodelimit"` - DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"` - DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"` - AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"` - IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"` - IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"` - DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"` - DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"` - DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"` + AddressRange string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"` + AddressRange6 string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"` + NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"` + NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified" swaggertype:"primitive,integer" format:"int64"` + NetworkLastModified int64 `json:"networklastmodified" bson:"networklastmodified" swaggertype:"primitive,integer" format:"int64"` + DefaultInterface string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"` + DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"` + NodeLimit int32 `json:"nodelimit" bson:"nodelimit"` + DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"` + DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"` + AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"` + IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"` + IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"` + DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"` + DefaultMTU int32 `json:"defaultmtu" bson:"defaultmtu"` + DefaultACL string `json:"defaultacl" bson:"defaultacl" yaml:"defaultacl" validate:"checkyesorno"` + NameServers []string `json:"dns_nameservers"` } // SaveData - sensitive fields of a network that should be kept the same diff --git a/models/node.go b/models/node.go index 48243540..72dc9ea5 100644 --- a/models/node.go +++ b/models/node.go @@ -5,6 +5,7 @@ import ( "math/rand" "net" "strings" + "sync" "time" "github.com/google/uuid" @@ -119,6 +120,7 @@ type Node struct { IsUserNode bool `json:"is_user_node"` StaticNode ExtClient `json:"static_node"` Status NodeStatus `json:"node_status"` + Mutex *sync.Mutex `json:"-"` } // LegacyNode - legacy struct for node model diff --git a/models/structs.go b/models/structs.go index 428e9bbf..7e9a5a44 100644 --- a/models/structs.go +++ b/models/structs.go @@ -151,12 +151,18 @@ type ExtPeersResponse struct { KeepAlive int32 `json:"persistentkeepalive" bson:"persistentkeepalive"` } +type EgressRangeMetric struct { + Network string `json:"network"` + RouteMetric uint32 `json:"route_metric"` // preffered range 1-999 +} + // EgressGatewayRequest - egress gateway request type EgressGatewayRequest struct { - NodeID string `json:"nodeid" bson:"nodeid"` - NetID string `json:"netid" bson:"netid"` - NatEnabled string `json:"natenabled" bson:"natenabled"` - Ranges []string `json:"ranges" bson:"ranges"` + NodeID string `json:"nodeid" bson:"nodeid"` + NetID string `json:"netid" bson:"netid"` + NatEnabled string `json:"natenabled" bson:"natenabled"` + Ranges []string `json:"ranges" bson:"ranges"` + RangesWithMetric []EgressRangeMetric `json:"ranges_with_metric"` } // RelayRequest - relay request struct diff --git a/pro/auth/auth.go b/pro/auth/auth.go index 49a34e73..162a6ce2 100644 --- a/pro/auth/auth.go +++ b/pro/auth/auth.go @@ -245,6 +245,12 @@ func getUserEmailFromClaims(token string) string { return "" } claims, _ := accessToken.Claims.(jwt.MapClaims) + if claims == nil { + return "" + } + if claims["email"] == nil { + return "" + } return claims["email"].(string) } diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index 7aa34953..8d059b46 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -199,6 +199,10 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) { if userInfo.Email == "" { userInfo.Email = getUserEmailFromClaims(token.AccessToken) } + if userInfo.Email == "" && userInfo.UserPrincipalName != "" { + userInfo.Email = userInfo.UserPrincipalName + + } if userInfo.Email == "" { err = errors.New("failed to fetch user email from SSO state") return userInfo, err diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 5cdc767c..f87fe020 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -1108,6 +1108,9 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { } gws := userGws[node.Network] + if extClient.DNS == "" { + extClient.DNS = node.IngressDNS + } extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) gws = append(gws, models.UserRemoteGws{ GwID: node.ID.String(), diff --git a/pro/email/email.go b/pro/email/email.go index 7411ae3e..cde69826 100644 --- a/pro/email/email.go +++ b/pro/email/email.go @@ -55,6 +55,6 @@ func GetClient() (e EmailSender) { } func IsValid(email string) bool { - emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) + emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`) return emailRegex.MatchString(email) }