| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 | package logicimport (	"errors"	"fmt"	"log/slog"	"maps"	"math/big"	"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{}	for _, iface := range h.Interfaces {		if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {			continue		}		addr, err := NormalizeCIDR(iface.Address.String())		if err == nil {			ranges = append(ranges, addr)		}		rangeWithMetric := models.EgressRangeMetric{			Network:           addr,			VirtualNATNetwork: iface.VirtualNATAddr.String(),			RouteMetric:       256,		}		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,	})}func ToIPNet(ipaddr net.IP) *net.IPNet {	addrIpNet := net.IPNet{		IP:   ipaddr,		Mask: net.CIDRMask(32, 32),	}	if addrIpNet.IP.To4() == nil {		addrIpNet.Mask = net.CIDRMask(128, 128)	}	return &addrIpNet}func MapExternalServicesToHostNodes(h *models.Host) {	if h.EgressServices == nil {		return	}	ranges := []string{}	rangesWithMetric := []models.EgressRangeMetric{}	// fetch namservers for internal dns	hostEgressServices := maps.Clone(h.EgressServices)	for _, nsI := range h.NameServers {		nsIP := net.ParseIP(nsI)		if nsIP.IsPrivate() {			hostEgressServices["DNS"] = append(hostEgressServices["DNS"], models.EgressIPNat{				EgressIP: nsIP,			})		}	}	for _, nodeID := range h.Nodes {		node, err := GetNodeByID(nodeID)		if err != nil {			continue		}		for i, egressServiceIPs := range hostEgressServices {			if len(egressServiceIPs) == 0 {				continue			}			for j, egressServiceIP := range egressServiceIPs {				currRangesWithMetric := GetEgressRangesWithMetric(models.NetworkID(node.Network))				addr := ToIPNet(egressServiceIP.EgressIP)				ranges = append(ranges, addr.String())				for _, iface := range h.Interfaces {					if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {						continue					}					ifaceAddr, err := NormalizeCIDRV1(iface.Address.String())					if err != nil {						continue					}					if !ifaceAddr.Contains(egressServiceIP.EgressIP) {						continue					}					egressNATIP, err := netmapTranslate(egressServiceIP.EgressIP, ifaceAddr.String(), iface.VirtualNATAddr.String())					if err != nil {						continue					}					egressServiceIP.NATIP = egressNATIP					egressServiceIPs[j] = egressServiceIP					rangeWithMetric := models.EgressRangeMetric{						Network:           addr.String(),						VirtualNATNetwork: ToIPNet(egressServiceIP.NATIP).String(),						RouteMetric:       256,					}					if currRangeMetric, ok := currRangesWithMetric[addr.String()]; ok {						lastMetricValue := currRangeMetric[len(currRangeMetric)-1]						rangeWithMetric.RouteMetric = lastMetricValue.RouteMetric + 10					}					rangesWithMetric = append(rangesWithMetric, rangeWithMetric)				}				h.EgressServices[i] = egressServiceIPs			}		}		if !node.IsEgressGateway {			fmt.Printf("Configuring EGRESS GW: Ranges: %+v, RANGE METRIC: %+v\n", ranges, rangesWithMetric)			_, err := CreateEgressGateway(models.EgressGatewayRequest{				NodeID:           node.ID.String(),				NetID:            node.Network,				NatEnabled:       "yes",				Ranges:           ranges,				RangesWithMetric: rangesWithMetric,			})			if err != nil {				slog.Error("failed to create egress node for external services ", "err", err.Error())			}		} else {			node.EgressGatewayRequest.Ranges = append(node.EgressGatewayRequest.Ranges, ranges...)			node.EgressGatewayRequest.RangesWithMetric = append(node.EgressGatewayRequest.RangesWithMetric, rangesWithMetric...)			node.EgressGatewayRanges = append(node.EgressGatewayRanges, ranges...)			UpsertNode(&node)		}	}	UpsertHost(h)}// netmapTranslate translates an IP address from one subnet to anotherfunc netmapTranslate(ip net.IP, srcCIDR, dstCIDR string) (net.IP, error) {	_, srcNet, err := net.ParseCIDR(srcCIDR)	if err != nil {		return nil, fmt.Errorf("invalid source CIDR: %w", err)	}	_, dstNet, err := net.ParseCIDR(dstCIDR)	if err != nil {		return nil, fmt.Errorf("invalid destination CIDR: %w", err)	}	// Ensure IP belongs to the source subnet	if !srcNet.Contains(ip) {		return nil, fmt.Errorf("IP %s not in source CIDR %s", ip, srcCIDR)	}	// Convert IPs to big.Int	ipInt := ipToBigInt(ip)	srcBase := ipToBigInt(srcNet.IP)	dstBase := ipToBigInt(dstNet.IP)	// Compute offset and apply it to the destination base	offset := new(big.Int).Sub(ipInt, srcBase)	translatedIP := new(big.Int).Add(dstBase, offset)	return bigIntToIP(translatedIP, ip), nil}// ipToBigInt converts an IP address to a big integerfunc ipToBigInt(ip net.IP) *big.Int {	return new(big.Int).SetBytes(ip.To16()) // Always use IPv6 representation}// bigIntToIP correctly converts a big integer back to an IP addressfunc bigIntToIP(bi *big.Int, originalIP net.IP) net.IP {	ipBytes := bi.Bytes()	// Ensure correct length (IPv4 = 4 bytes, IPv6 = 16 bytes)	if len(originalIP) == net.IPv4len && len(ipBytes) > 4 {		ipBytes = ipBytes[len(ipBytes)-4:] // Extract last 4 bytes for IPv4	} else if len(ipBytes) < len(originalIP) {		padding := make([]byte, len(originalIP)-len(ipBytes))		ipBytes = append(padding, ipBytes...)	}	return net.IP(ipBytes)}// isConflicting checks if a given CIDR conflicts with existing subnetsfunc 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 CIDRfunc AssignVirtualNATs(h *models.Host) error {	existingNets := []net.IPNet{}	for _, nodeID := range h.Nodes {		node, err := GetNodeByID(nodeID)		if err == nil {			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 {		if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {			continue		}		// 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.64.%d.0/%d", ipv4Index, ones)			ipv4Index++		} else { // IPv6 case			newSubnet = fmt.Sprintf("fd00:64:%d::/%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 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}// GetAllEgresses - gets all the nodes that are egressesfunc 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 gatewayfunc 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 modelfunc ValidateEgressGateway(gateway models.EgressGatewayRequest) error {	return nil}// DeleteEgressGateway - deletes egress from nodefunc 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}
 |