Browse Source

added more uniform unique address checks

0xdcarns 3 years ago
parent
commit
611a425852
6 changed files with 132 additions and 49 deletions
  1. 1 0
      go.mod
  2. 2 0
      go.sum
  3. 56 0
      logic/ips/ips.go
  4. 62 47
      logic/networks.go
  5. 9 1
      models/network.go
  6. 2 1
      models/node.go

+ 1 - 0
go.mod

@@ -66,6 +66,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.8.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/seancfoley/ipaddress-go/ipaddr v0.0.0-20220113215635-21f206932b4b // indirect
 	github.com/spf13/afero v1.3.2 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect

+ 2 - 0
go.sum

@@ -209,6 +209,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/seancfoley/ipaddress-go/ipaddr v0.0.0-20220113215635-21f206932b4b h1:JE6/MonwfmJEAmlvHMvOxuvyvTENYQWAp/cPE9I76P8=
+github.com/seancfoley/ipaddress-go/ipaddr v0.0.0-20220113215635-21f206932b4b/go.mod h1:a9MEg04Z1myhjCrB4iU/ZeQs8fBYdSI/LnDbJ7n5Nb8=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=

+ 56 - 0
logic/ips/ips.go

@@ -0,0 +1,56 @@
+package ips
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/seancfoley/ipaddress-go/ipaddr"
+)
+
+// GetFirstAddr - gets the first valid address in a given IPv4 CIDR
+func GetFirstAddr(cidr4 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr4).GetAddress()
+	if !currentCidr.IsIPv4() {
+		return nil, fmt.Errorf("invalid IPv4 CIDR provided to GetFirstAddr")
+	}
+	lower := currentCidr.GetLower()
+	ipParts := strings.Split(lower.GetNetIPAddr().IP.String(), ".")
+	if ipParts[len(ipParts)-1] == "0" {
+		lower = lower.Increment(1)
+	}
+	return lower, nil
+}
+
+// GetLastAddr - gets the last valid address in a given IPv4 CIDR
+func GetLastAddr(cidr4 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr4).GetAddress()
+	if !currentCidr.IsIPv4() {
+		return nil, fmt.Errorf("invalid IPv4 CIDR provided to GetLastAddr")
+	}
+	upper := currentCidr.GetUpper()
+	ipParts := strings.Split(upper.GetNetIPAddr().IP.String(), ".")
+	if ipParts[len(ipParts)-1] == "255" {
+		upper = upper.Increment(-1)
+	}
+	return upper, nil
+}
+
+// GetFirstAddr6 - gets the first valid IPv6 address in a given IPv6 CIDR
+func GetFirstAddr6(cidr6 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr6).GetAddress()
+	if !currentCidr.IsIPv6() {
+		return nil, fmt.Errorf("invalid IPv6 CIDR provided to GetFirstAddr6")
+	}
+	lower := currentCidr.GetLower()
+	return lower, nil
+}
+
+// GetLastAddr6 - gets the last valid IPv6 address in a given IPv6 CIDR
+func GetLastAddr6(cidr6 string) (*ipaddr.IPAddress, error) {
+	currentCidr := ipaddr.NewIPAddressString(cidr6).GetAddress()
+	if !currentCidr.IsIPv6() {
+		return nil, fmt.Errorf("invalid IPv6 CIDR provided to GetLastAddr6")
+	}
+	lower := currentCidr.GetLower()
+	return lower, nil
+}

+ 62 - 47
logic/networks.go

@@ -1,7 +1,6 @@
 package logic
 
 import (
-	"encoding/binary"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -13,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
+	"github.com/gravitl/netmaker/logic/ips"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/validation"
@@ -178,29 +178,28 @@ func UniqueAddress(networkName string) (string, error) {
 	var network models.Network
 	network, err := GetParentNetwork(networkName)
 	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
+		logger.Log(0, "UniqueAddressServer encountered  an error")
 		return "666", err
 	}
 
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange)
+	if network.IsIPv4 == "no" {
+		return "", fmt.Errorf("IPv4 not active on network " + networkName)
+	}
+
+	newAddr, err := ips.GetFirstAddr(network.AddressRange)
 	if err != nil {
-		fmt.Println("UniqueAddress encountered  an error")
+		logger.Log(0, "UniqueAddressServer encountered  an error")
 		return "666", err
 	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
-		}
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) {
-			return ip.String(), err
+
+	for ; newAddr.ToAddressString().IsValid(); newAddr = newAddr.Increment(1) {
+		if IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, false) &&
+			IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, false) {
+			return newAddr.GetNetIPAddr().IP.String(), nil
 		}
 	}
 
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
+	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", errors.New("ERROR: No unique addresses available. Check network subnet")
 }
 
 // UniqueAddressServer - get unique address starting from last available
@@ -213,27 +212,48 @@ func UniqueAddressServer(networkName string) (string, error) {
 		return "666", err
 	}
 
-	_, ipv4Net, err := net.ParseCIDR(network.AddressRange)
+	if network.IsIPv4 == "no" {
+		return "", fmt.Errorf("IPv4 not active on network " + networkName)
+	}
+
+	newAddr, err := ips.GetLastAddr(network.AddressRange)
+	if err != nil {
+		logger.Log(0, "UniqueAddressServer encountered  an error")
+		return "666", err
+	}
+
+	for ; newAddr.ToAddressString().IsValid(); newAddr = newAddr.Increment(-1) {
+		if IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, false) &&
+			IsIPUnique(networkName, newAddr.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, false) {
+			return newAddr.GetNetIPAddr().IP.String(), nil
+		}
+	}
+
+	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", fmt.Errorf("no unique server addresses found")
+}
+
+// UniqueAddress6Server - get unique address starting from last available
+func UniqueAddress6Server(networkName string) (string, error) {
+
+	network, err := GetParentNetwork(networkName)
 	if err != nil {
 		logger.Log(0, "UniqueAddressServer encountered  an error")
 		return "666", err
 	}
 
-	// convert IPNet struct mask and address to uint32
-	// network is BigEndian
-	mask := binary.BigEndian.Uint32(ipv4Net.Mask)
-	start := binary.BigEndian.Uint32(ipv4Net.IP)
+	if network.IsIPv6 == "no" {
+		return "", fmt.Errorf("IPv6 not active on network " + networkName)
+	}
 
-	// find the final address
-	finish := (start & mask) | (mask ^ 0xffffffff)
+	newAddr6, err := ips.GetLastAddr6(network.AddressRange6)
+	if err != nil {
+		return "666", err
+	}
 
-	// loop through addresses as uint32
-	for i := finish - 1; i > start; i-- {
-		// convert back to net.IP
-		ip := make(net.IP, 4)
-		binary.BigEndian.PutUint32(ip, i)
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) {
-			return ip.String(), err
+	for ; newAddr6.ToAddressString().IsValid(); newAddr6 = newAddr6.Increment(-1) {
+		if IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, true) &&
+			IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, true) {
+			return newAddr6.GetNetIPAddr().IP.String(), nil
 		}
 	}
 
@@ -278,28 +298,23 @@ func UniqueAddress6(networkName string) (string, error) {
 		fmt.Println("Network Not Found")
 		return "", err
 	}
-	if network.IsDualStack == "no" {
-		return "", nil
+	if network.IsIPv6 == "no" {
+		return "", fmt.Errorf("IPv6 not active on network " + networkName)
 	}
 
-	offset := true
-	ip, ipnet, err := net.ParseCIDR(network.AddressRange6)
+	newAddr6, err := ips.GetFirstAddr6(network.AddressRange6)
 	if err != nil {
-		fmt.Println("UniqueAddress6 encountered  an error")
 		return "666", err
 	}
-	for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
-		if offset {
-			offset = false
-			continue
-		}
-		if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, true) {
-			return ip.String(), err
+
+	for ; newAddr6.ToAddressString().IsValid(); newAddr6 = newAddr6.Increment(1) {
+		if IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.NODES_TABLE_NAME, true) &&
+			IsIPUnique(networkName, newAddr6.GetNetIPAddr().IP.String(), database.EXT_CLIENT_TABLE_NAME, true) {
+			return newAddr6.GetNetIPAddr().IP.String(), nil
 		}
 	}
-	//TODO
-	err1 := errors.New("ERROR: No unique addresses available. Check network subnet")
-	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
+
+	return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", errors.New("ERROR: No unique IPv6 addresses available. Check network subnet")
 }
 
 // GetLocalIP - gets the local ip
@@ -561,7 +576,7 @@ func GetNetwork(networkname string) (models.Network, error) {
 	return network, nil
 }
 
-// Network.NetIDInNetworkCharSet - checks if a netid of a network uses valid characters
+// NetIDInNetworkCharSet - checks if a netid of a network uses valid characters
 func NetIDInNetworkCharSet(network *models.Network) bool {
 
 	charset := "abcdefghijklmnopqrstuvwxyz1234567890-_."
@@ -574,7 +589,7 @@ func NetIDInNetworkCharSet(network *models.Network) bool {
 	return true
 }
 
-// Network.Validate - validates fields of an network struct
+// Validate - validates fields of an network struct
 func ValidateNetwork(network *models.Network, isUpdate bool) error {
 	v := validator.New()
 	_ = v.RegisterValidation("netid_valid", func(fl validator.FieldLevel) bool {
@@ -637,7 +652,7 @@ func KeyUpdate(netname string) (models.Network, error) {
 	return models.Network{}, nil
 }
 
-//SaveNetwork - save network struct to database
+// SaveNetwork - save network struct to database
 func SaveNetwork(network *models.Network) error {
 	data, err := json.Marshal(network)
 	if err != nil {

+ 9 - 1
models/network.go

@@ -21,7 +21,7 @@ type Network struct {
 	AccessKeys          []AccessKey `json:"accesskeys" bson:"accesskeys"`
 	AllowManualSignUp   string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
 	IsLocal             string      `json:"islocal" bson:"islocal" validate:"checkyesorno"`
-	IsDualStack         string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"`
+	IsDualStack         string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"` // ** IsDualStack deprecated **
 	IsIPv4              string      `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
 	IsIPv6              string      `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
 	IsPointToSite       string      `json:"ispointtosite" bson:"ispointtosite" validate:"checkyesorno"`
@@ -88,6 +88,14 @@ func (network *Network) SetDefaults() {
 		network.IsIPv4 = "yes"
 	}
 
+	if network.IsIPv4 == "" {
+		network.IsIPv4 = "yes"
+	}
+
+	if network.IsIPv6 == "" {
+		network.IsIPv6 = "no"
+	}
+
 	if network.DefaultMTU == 0 {
 		network.DefaultMTU = 1280
 	}

+ 2 - 1
models/node.go

@@ -70,7 +70,8 @@ type Node struct {
 	IsStatic            string   `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
 	UDPHolePunch        string   `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
 	//PullChanges         string      `json:"pullchanges" bson:"pullchanges" yaml:"pullchanges" validate:"checkyesorno"`
-	DNSOn        string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
+	DNSOn string `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
+	// ** IsDualStack deprecated **
 	IsDualStack  string      `json:"isdualstack" bson:"isdualstack" yaml:"isdualstack" validate:"checkyesorno"`
 	IsServer     string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
 	Action       string      `json:"action" bson:"action" yaml:"action"`