Kaynağa Gözat

auto configure egress

abhishek9686 5 ay önce
ebeveyn
işleme
c7edc483a2
6 değiştirilmiş dosya ile 282 ekleme ve 222 silme
  1. 1 24
      auth/host_session.go
  2. 0 70
      logic/acls.go
  3. 274 0
      logic/egress.go
  4. 0 120
      logic/gateway.go
  5. 4 3
      models/node.go
  6. 3 5
      models/structs.go

+ 1 - 24
auth/host_session.go

@@ -275,30 +275,7 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
 				}
 			}
 			if autoEgress {
-				currRangesWithMetric := logic.GetEgressRangesWithMetric(models.NetworkID(newNode.Network))
-				ranges := []string{}
-				rangesWithMetric := []models.EgressRangeMetric{}
-				for _, iface := range h.Interfaces {
-					addr, err := logic.NormalizeCIDR(iface.Address.String())
-					if err == nil {
-						ranges = append(ranges, addr)
-					}
-					rangeWithMetric := models.EgressRangeMetric{
-						Network: addr,
-					}
-					if currRangeMetric, ok := currRangesWithMetric[addr]; ok {
-						lastMetricValue := currRangeMetric[len(currRangeMetric)-1]
-						rangeWithMetric.RouteMetric = lastMetricValue.RouteMetric + 10
-					}
-					rangesWithMetric = append(rangesWithMetric, rangeWithMetric)
-				}
-				logic.CreateEgressGateway(models.EgressGatewayRequest{
-					NodeID:           newNode.ID.String(),
-					NetID:            newNode.Network,
-					NatEnabled:       "yes",
-					Ranges:           ranges,
-					RangesWithMetric: rangesWithMetric,
-				})
+				logic.AutoConfigureEgress(h, newNode)
 			}
 			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
 			hostactions.AddAction(models.HostUpdate{

+ 0 - 70
logic/acls.go

@@ -223,76 +223,6 @@ func IsAclExists(aclID string) bool {
 	_, err := GetAcl(aclID)
 	return err == nil
 }
-func GetEgressRanges(netID models.NetworkID) (map[string][]string, map[string]struct{}, error) {
-
-	resultMap := make(map[string]struct{})
-	nodeEgressMap := make(map[string][]string)
-	networkNodes, err := GetNetworkNodes(netID.String())
-	if err != nil {
-		return nil, nil, err
-	}
-	for _, currentNode := range networkNodes {
-		if currentNode.Network != netID.String() {
-			continue
-		}
-		if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
-			if len(currentNode.EgressGatewayRanges) > 0 {
-				nodeEgressMap[currentNode.ID.String()] = currentNode.EgressGatewayRanges
-				for _, egressRangeI := range currentNode.EgressGatewayRanges {
-					resultMap[egressRangeI] = struct{}{}
-				}
-			}
-		}
-	}
-	extclients, _ := GetNetworkExtClients(netID.String())
-	for _, extclient := range extclients {
-		if len(extclient.ExtraAllowedIPs) > 0 {
-			nodeEgressMap[extclient.ClientID] = extclient.ExtraAllowedIPs
-			for _, extraAllowedIP := range extclient.ExtraAllowedIPs {
-				resultMap[extraAllowedIP] = struct{}{}
-			}
-		}
-	}
-	return nodeEgressMap, resultMap, nil
-}
-func sortRouteMetricByAscending(items []models.EgressRangeMetric) []models.EgressRangeMetric {
-	sort.Slice(items, func(i, j int) bool {
-		return items[i].RouteMetric < items[j].RouteMetric
-	})
-	return items
-}
-
-func GetEgressRangesWithMetric(netID models.NetworkID) map[string][]models.EgressRangeMetric {
-
-	egressMap := make(map[string][]models.EgressRangeMetric)
-	networkNodes, err := GetNetworkNodes(netID.String())
-	if err != nil {
-		return nil
-	}
-	for _, currentNode := range networkNodes {
-		if currentNode.Network != netID.String() {
-			continue
-		}
-		if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
-			if len(currentNode.EgressGatewayRequest.RangesWithMetric) > 0 {
-				for _, egressRangeI := range currentNode.EgressGatewayRequest.RangesWithMetric {
-					if value, ok := egressMap[egressRangeI.Network]; ok {
-						value = append(value, egressRangeI)
-						egressMap[egressRangeI.Network] = value
-					} else {
-						egressMap[egressRangeI.Network] = []models.EgressRangeMetric{
-							egressRangeI,
-						}
-					}
-				}
-			}
-		}
-	}
-	for key, value := range egressMap {
-		egressMap[key] = sortRouteMetricByAscending(value)
-	}
-	return egressMap
-}
 
 func checkIfAclTagisValid(t models.AclPolicyTag, netID models.NetworkID, policyType models.AclPolicyType, isSrc bool) bool {
 	switch t.ID {

+ 274 - 0
logic/egress.go

@@ -0,0 +1,274 @@
+package logic
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"slices"
+	"sort"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+func AutoConfigureEgress(h *models.Host, node *models.Node) {
+	currRangesWithMetric := GetEgressRangesWithMetric(models.NetworkID(node.Network))
+	ranges := []string{}
+	rangesWithMetric := []models.EgressRangeMetric{}
+	assignVirtualNATs(h, node)
+	for _, iface := range h.Interfaces {
+		addr, err := NormalizeCIDR(iface.Address.String())
+		if err == nil {
+			ranges = append(ranges, addr)
+		}
+		rangeWithMetric := models.EgressRangeMetric{
+			Network:           addr,
+			VirtualNATNetwork: iface.VirtualNATAddr.String(),
+		}
+		if currRangeMetric, ok := currRangesWithMetric[addr]; ok {
+			lastMetricValue := currRangeMetric[len(currRangeMetric)-1]
+			rangeWithMetric.RouteMetric = lastMetricValue.RouteMetric + 10
+		}
+		rangesWithMetric = append(rangesWithMetric, rangeWithMetric)
+	}
+	CreateEgressGateway(models.EgressGatewayRequest{
+		NodeID:           node.ID.String(),
+		NetID:            node.Network,
+		NatEnabled:       "yes",
+		Ranges:           ranges,
+		RangesWithMetric: rangesWithMetric,
+	})
+}
+
+// isConflicting checks if a given CIDR conflicts with existing subnets
+func isConflicting(cidr *net.IPNet, existing []net.IPNet) bool {
+	for _, net := range existing {
+		if cidr.Contains(net.IP) || net.Contains(cidr.IP) {
+			return true
+		}
+	}
+	return false
+}
+
+// assignVirtualNATs assigns a unique virtual NAT subnet for each interface CIDR
+func assignVirtualNATs(h *models.Host, node *models.Node) error {
+	existingNets := []net.IPNet{}
+	existingNets = append(existingNets, node.NetworkRange, node.Address6)
+	for _, iface := range h.Interfaces {
+		existingNets = append(existingNets, iface.Address)
+	}
+	ipv4Index := 1
+	ipv6Index := 1
+	for i, iface := range h.Interfaces {
+		// Preserve the original subnet mask
+		ones, _ := iface.Address.Mask.Size()
+
+		var newSubnet string
+		if iface.Address.IP.To4() != nil { // IPv4 case
+			newSubnet = fmt.Sprintf("10.200.%d.0/%d", ipv4Index, ones)
+			ipv4Index++
+		} else { // IPv6 case
+			newSubnet = fmt.Sprintf("fd00:200:%x::/%d", ipv6Index, ones)
+			ipv6Index++
+		}
+
+		_, candidateNet, _ := net.ParseCIDR(newSubnet)
+
+		// Ensure no conflicts
+		if !isConflicting(candidateNet, existingNets) {
+			_, newSubnetCidr, _ := net.ParseCIDR(newSubnet)
+			h.Interfaces[i].VirtualNATAddr = *newSubnetCidr
+		} else {
+			return fmt.Errorf("could not find non-conflicting subnet for %s", iface.Address.String())
+		}
+	}
+	return UpsertHost(h)
+}
+
+func GetEgressRanges(netID models.NetworkID) (map[string][]string, map[string]struct{}, error) {
+
+	resultMap := make(map[string]struct{})
+	nodeEgressMap := make(map[string][]string)
+	networkNodes, err := GetNetworkNodes(netID.String())
+	if err != nil {
+		return nil, nil, err
+	}
+	for _, currentNode := range networkNodes {
+		if currentNode.Network != netID.String() {
+			continue
+		}
+		if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
+			if len(currentNode.EgressGatewayRanges) > 0 {
+				nodeEgressMap[currentNode.ID.String()] = currentNode.EgressGatewayRanges
+				for _, egressRangeI := range currentNode.EgressGatewayRanges {
+					resultMap[egressRangeI] = struct{}{}
+				}
+			}
+		}
+	}
+	extclients, _ := GetNetworkExtClients(netID.String())
+	for _, extclient := range extclients {
+		if len(extclient.ExtraAllowedIPs) > 0 {
+			nodeEgressMap[extclient.ClientID] = extclient.ExtraAllowedIPs
+			for _, extraAllowedIP := range extclient.ExtraAllowedIPs {
+				resultMap[extraAllowedIP] = struct{}{}
+			}
+		}
+	}
+	return nodeEgressMap, resultMap, nil
+}
+func sortRouteMetricByAscending(items []models.EgressRangeMetric) []models.EgressRangeMetric {
+	sort.Slice(items, func(i, j int) bool {
+		return items[i].RouteMetric < items[j].RouteMetric
+	})
+	return items
+}
+
+func GetEgressRangesWithMetric(netID models.NetworkID) map[string][]models.EgressRangeMetric {
+
+	egressMap := make(map[string][]models.EgressRangeMetric)
+	networkNodes, err := GetNetworkNodes(netID.String())
+	if err != nil {
+		return nil
+	}
+	for _, currentNode := range networkNodes {
+		if currentNode.Network != netID.String() {
+			continue
+		}
+		if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
+			if len(currentNode.EgressGatewayRequest.RangesWithMetric) > 0 {
+				for _, egressRangeI := range currentNode.EgressGatewayRequest.RangesWithMetric {
+					if value, ok := egressMap[egressRangeI.Network]; ok {
+						value = append(value, egressRangeI)
+						egressMap[egressRangeI.Network] = value
+					} else {
+						egressMap[egressRangeI.Network] = []models.EgressRangeMetric{
+							egressRangeI,
+						}
+					}
+				}
+			}
+		}
+	}
+	for key, value := range egressMap {
+		egressMap[key] = sortRouteMetricByAscending(value)
+	}
+	return egressMap
+}
+
+// GetAllEgresses - gets all the nodes that are egresses
+func GetAllEgresses() ([]models.Node, error) {
+	nodes, err := GetAllNodes()
+	if err != nil {
+		return nil, err
+	}
+	egresses := make([]models.Node, 0)
+	for _, node := range nodes {
+		if node.IsEgressGateway {
+			egresses = append(egresses, node)
+		}
+	}
+	return egresses, nil
+}
+
+// CreateEgressGateway - creates an egress gateway
+func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
+	node, err := GetNodeByID(gateway.NodeID)
+	if err != nil {
+		return models.Node{}, err
+	}
+	host, err := GetHost(node.HostID.String())
+	if err != nil {
+		return models.Node{}, err
+	}
+	if host.OS != "linux" { // support for other OS to be added
+		return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
+	}
+	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" {
+			// remove inet range
+			gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
+			continue
+		}
+		normalized, err := NormalizeCIDR(gateway.Ranges[i])
+		if err != nil {
+			return models.Node{}, err
+		}
+		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"
+	}
+	err = ValidateEgressGateway(gateway)
+	if err != nil {
+		return models.Node{}, err
+	}
+	if gateway.Ranges == nil {
+		gateway.Ranges = make([]string, 0)
+	}
+	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 {
+		return models.Node{}, err
+	}
+	return node, nil
+}
+
+// ValidateEgressGateway - validates the egress gateway model
+func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
+	return nil
+}
+
+// DeleteEgressGateway - deletes egress from node
+func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
+	node, err := GetNodeByID(nodeid)
+	if err != nil {
+		return models.Node{}, err
+	}
+	node.IsEgressGateway = false
+	node.EgressGatewayRanges = []string{}
+	node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
+	node.SetLastModified()
+	if err = UpsertNode(&node); err != nil {
+		return models.Node{}, err
+	}
+	return node, nil
+}

+ 0 - 120
logic/gateway.go

@@ -3,8 +3,6 @@ package logic
 import (
 	"errors"
 	"fmt"
-	"slices"
-	"sort"
 	"time"
 
 	"github.com/gravitl/netmaker/database"
@@ -48,124 +46,6 @@ func GetAllIngresses() ([]models.Node, error) {
 	return ingresses, nil
 }
 
-// GetAllEgresses - gets all the nodes that are egresses
-func GetAllEgresses() ([]models.Node, error) {
-	nodes, err := GetAllNodes()
-	if err != nil {
-		return nil, err
-	}
-	egresses := make([]models.Node, 0)
-	for _, node := range nodes {
-		if node.IsEgressGateway {
-			egresses = append(egresses, node)
-		}
-	}
-	return egresses, nil
-}
-
-// CreateEgressGateway - creates an egress gateway
-func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
-	node, err := GetNodeByID(gateway.NodeID)
-	if err != nil {
-		return models.Node{}, err
-	}
-	host, err := GetHost(node.HostID.String())
-	if err != nil {
-		return models.Node{}, err
-	}
-	if host.OS != "linux" { // support for other OS to be added
-		return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
-	}
-	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" {
-			// remove inet range
-			gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
-			continue
-		}
-		normalized, err := NormalizeCIDR(gateway.Ranges[i])
-		if err != nil {
-			return models.Node{}, err
-		}
-		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"
-	}
-	err = ValidateEgressGateway(gateway)
-	if err != nil {
-		return models.Node{}, err
-	}
-	if gateway.Ranges == nil {
-		gateway.Ranges = make([]string, 0)
-	}
-	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 {
-		return models.Node{}, err
-	}
-	return node, nil
-}
-
-// ValidateEgressGateway - validates the egress gateway model
-func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
-	return nil
-}
-
-// DeleteEgressGateway - deletes egress from node
-func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
-	node, err := GetNodeByID(nodeid)
-	if err != nil {
-		return models.Node{}, err
-	}
-	node.IsEgressGateway = false
-	node.EgressGatewayRanges = []string{}
-	node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
-	node.SetLastModified()
-	if err = UpsertNode(&node); err != nil {
-		return models.Node{}, err
-	}
-	return node, nil
-}
-
 // CreateIngressGateway - creates an ingress gateway
 func CreateIngressGateway(netid string, nodeid string, ingress models.IngressRequest) (models.Node, error) {
 

+ 4 - 3
models/node.go

@@ -60,9 +60,10 @@ type NodeCheckin struct {
 
 // Iface struct for local interfaces of a node
 type Iface struct {
-	Name          string    `json:"name"`
-	Address       net.IPNet `json:"address"`
-	AddressString string    `json:"addressString"`
+	Name           string    `json:"name"`
+	Address        net.IPNet `json:"address"`
+	AddressString  string    `json:"addressString"`
+	VirtualNATAddr net.IPNet `json:"virtual_nat_addr"`
 }
 
 // CommonNode - represents a commonn node data elements shared by netmaker and netclient

+ 3 - 5
models/structs.go

@@ -154,8 +154,9 @@ type ExtPeersResponse struct {
 }
 
 type EgressRangeMetric struct {
-	Network     string `json:"network"`
-	RouteMetric uint32 `json:"route_metric"` // preffered range 1-999
+	Network           string `json:"network"`
+	RouteMetric       uint32 `json:"route_metric"` // preferred range 1-999
+	VirtualNATNetwork string `json:"virtual_nat_network"`
 }
 
 // EgressGatewayRequest - egress gateway request
@@ -238,9 +239,6 @@ type HostPull struct {
 	EndpointDetection bool                  `json:"endpoint_detection"`
 }
 
-type DefaultGwInfo struct {
-}
-
 // NodeGet - struct for a single node get response
 type NodeGet struct {
 	Node         Node                 `json:"node" bson:"node" yaml:"node"`