egress.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. package logic
  2. import (
  3. "errors"
  4. "fmt"
  5. "log/slog"
  6. "maps"
  7. "math/big"
  8. "net"
  9. "slices"
  10. "sort"
  11. "github.com/gravitl/netmaker/models"
  12. )
  13. func AutoConfigureEgress(h *models.Host, node *models.Node) {
  14. currRangesWithMetric := GetEgressRangesWithMetric(models.NetworkID(node.Network))
  15. ranges := []string{}
  16. rangesWithMetric := []models.EgressRangeMetric{}
  17. for _, iface := range h.Interfaces {
  18. if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {
  19. continue
  20. }
  21. addr, err := NormalizeCIDR(iface.Address.String())
  22. if err == nil {
  23. ranges = append(ranges, addr)
  24. }
  25. rangeWithMetric := models.EgressRangeMetric{
  26. Network: addr,
  27. VirtualNATNetwork: iface.VirtualNATAddr.String(),
  28. RouteMetric: 256,
  29. }
  30. if currRangeMetric, ok := currRangesWithMetric[addr]; ok {
  31. lastMetricValue := currRangeMetric[len(currRangeMetric)-1]
  32. rangeWithMetric.RouteMetric = lastMetricValue.RouteMetric + 10
  33. }
  34. rangesWithMetric = append(rangesWithMetric, rangeWithMetric)
  35. }
  36. CreateEgressGateway(models.EgressGatewayRequest{
  37. NodeID: node.ID.String(),
  38. NetID: node.Network,
  39. NatEnabled: "yes",
  40. Ranges: ranges,
  41. RangesWithMetric: rangesWithMetric,
  42. })
  43. }
  44. func ToIPNet(ipaddr net.IP) *net.IPNet {
  45. addrIpNet := net.IPNet{
  46. IP: ipaddr,
  47. Mask: net.CIDRMask(32, 32),
  48. }
  49. if addrIpNet.IP.To4() == nil {
  50. addrIpNet.Mask = net.CIDRMask(128, 128)
  51. }
  52. return &addrIpNet
  53. }
  54. func MapExternalServicesToHostNodes(h *models.Host) {
  55. if h.EgressServices == nil {
  56. return
  57. }
  58. ranges := []string{}
  59. rangesWithMetric := []models.EgressRangeMetric{}
  60. // fetch namservers for internal dns
  61. hostEgressServices := maps.Clone(h.EgressServices)
  62. for _, nsI := range h.NameServers {
  63. nsIP := net.ParseIP(nsI)
  64. if nsIP.IsPrivate() {
  65. hostEgressServices["DNS"] = append(hostEgressServices["DNS"], models.EgressIPNat{
  66. EgressIP: nsIP,
  67. })
  68. }
  69. }
  70. for _, nodeID := range h.Nodes {
  71. node, err := GetNodeByID(nodeID)
  72. if err != nil {
  73. continue
  74. }
  75. for i, egressServiceIPs := range hostEgressServices {
  76. if len(egressServiceIPs) == 0 {
  77. continue
  78. }
  79. for j, egressServiceIP := range egressServiceIPs {
  80. currRangesWithMetric := GetEgressRangesWithMetric(models.NetworkID(node.Network))
  81. addr := ToIPNet(egressServiceIP.EgressIP)
  82. ranges = append(ranges, addr.String())
  83. for _, iface := range h.Interfaces {
  84. if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {
  85. continue
  86. }
  87. ifaceAddr, err := NormalizeCIDRV1(iface.Address.String())
  88. if err != nil {
  89. continue
  90. }
  91. if !ifaceAddr.Contains(egressServiceIP.EgressIP) {
  92. continue
  93. }
  94. egressNATIP, err := netmapTranslate(egressServiceIP.EgressIP, ifaceAddr.String(), iface.VirtualNATAddr.String())
  95. if err != nil {
  96. continue
  97. }
  98. egressServiceIP.NATIP = egressNATIP
  99. egressServiceIPs[j] = egressServiceIP
  100. rangeWithMetric := models.EgressRangeMetric{
  101. Network: addr.String(),
  102. VirtualNATNetwork: ToIPNet(egressServiceIP.NATIP).String(),
  103. RouteMetric: 256,
  104. }
  105. if currRangeMetric, ok := currRangesWithMetric[addr.String()]; ok {
  106. lastMetricValue := currRangeMetric[len(currRangeMetric)-1]
  107. rangeWithMetric.RouteMetric = lastMetricValue.RouteMetric + 10
  108. }
  109. rangesWithMetric = append(rangesWithMetric, rangeWithMetric)
  110. }
  111. h.EgressServices[i] = egressServiceIPs
  112. }
  113. }
  114. if !node.IsEgressGateway {
  115. fmt.Printf("Configuring EGRESS GW: Ranges: %+v, RANGE METRIC: %+v\n", ranges, rangesWithMetric)
  116. _, err := CreateEgressGateway(models.EgressGatewayRequest{
  117. NodeID: node.ID.String(),
  118. NetID: node.Network,
  119. NatEnabled: "yes",
  120. Ranges: ranges,
  121. RangesWithMetric: rangesWithMetric,
  122. })
  123. if err != nil {
  124. slog.Error("failed to create egress node for external services ", "err", err.Error())
  125. }
  126. } else {
  127. node.EgressGatewayRequest.Ranges = append(node.EgressGatewayRequest.Ranges, ranges...)
  128. node.EgressGatewayRequest.RangesWithMetric = append(node.EgressGatewayRequest.RangesWithMetric, rangesWithMetric...)
  129. node.EgressGatewayRanges = append(node.EgressGatewayRanges, ranges...)
  130. UpsertNode(&node)
  131. }
  132. }
  133. UpsertHost(h)
  134. }
  135. // netmapTranslate translates an IP address from one subnet to another
  136. func netmapTranslate(ip net.IP, srcCIDR, dstCIDR string) (net.IP, error) {
  137. _, srcNet, err := net.ParseCIDR(srcCIDR)
  138. if err != nil {
  139. return nil, fmt.Errorf("invalid source CIDR: %w", err)
  140. }
  141. _, dstNet, err := net.ParseCIDR(dstCIDR)
  142. if err != nil {
  143. return nil, fmt.Errorf("invalid destination CIDR: %w", err)
  144. }
  145. // Ensure IP belongs to the source subnet
  146. if !srcNet.Contains(ip) {
  147. return nil, fmt.Errorf("IP %s not in source CIDR %s", ip, srcCIDR)
  148. }
  149. // Convert IPs to big.Int
  150. ipInt := ipToBigInt(ip)
  151. srcBase := ipToBigInt(srcNet.IP)
  152. dstBase := ipToBigInt(dstNet.IP)
  153. // Compute offset and apply it to the destination base
  154. offset := new(big.Int).Sub(ipInt, srcBase)
  155. translatedIP := new(big.Int).Add(dstBase, offset)
  156. return bigIntToIP(translatedIP, ip), nil
  157. }
  158. // ipToBigInt converts an IP address to a big integer
  159. func ipToBigInt(ip net.IP) *big.Int {
  160. return new(big.Int).SetBytes(ip.To16()) // Always use IPv6 representation
  161. }
  162. // bigIntToIP correctly converts a big integer back to an IP address
  163. func bigIntToIP(bi *big.Int, originalIP net.IP) net.IP {
  164. ipBytes := bi.Bytes()
  165. // Ensure correct length (IPv4 = 4 bytes, IPv6 = 16 bytes)
  166. if len(originalIP) == net.IPv4len && len(ipBytes) > 4 {
  167. ipBytes = ipBytes[len(ipBytes)-4:] // Extract last 4 bytes for IPv4
  168. } else if len(ipBytes) < len(originalIP) {
  169. padding := make([]byte, len(originalIP)-len(ipBytes))
  170. ipBytes = append(padding, ipBytes...)
  171. }
  172. return net.IP(ipBytes)
  173. }
  174. // isConflicting checks if a given CIDR conflicts with existing subnets
  175. func isConflicting(cidr *net.IPNet, existing []net.IPNet) bool {
  176. for _, net := range existing {
  177. if cidr.Contains(net.IP) || net.Contains(cidr.IP) {
  178. return true
  179. }
  180. }
  181. return false
  182. }
  183. // AssignVirtualNATs assigns a unique virtual NAT subnet for each interface CIDR
  184. func AssignVirtualNATs(h *models.Host) error {
  185. existingNets := []net.IPNet{}
  186. for _, nodeID := range h.Nodes {
  187. node, err := GetNodeByID(nodeID)
  188. if err == nil {
  189. existingNets = append(existingNets, node.NetworkRange, node.Address6)
  190. }
  191. }
  192. for _, iface := range h.Interfaces {
  193. existingNets = append(existingNets, iface.Address)
  194. }
  195. ipv4Index := 1
  196. ipv6Index := 1
  197. for i, iface := range h.Interfaces {
  198. if !iface.Address.IP.IsPrivate() || iface.Name == h.DefaultInterface {
  199. continue
  200. }
  201. // Preserve the original subnet mask
  202. ones, _ := iface.Address.Mask.Size()
  203. var newSubnet string
  204. if iface.Address.IP.To4() != nil { // IPv4 case
  205. newSubnet = fmt.Sprintf("10.64.%d.0/%d", ipv4Index, ones)
  206. ipv4Index++
  207. } else { // IPv6 case
  208. newSubnet = fmt.Sprintf("fd00:64:%d::/%d", ipv6Index, ones)
  209. ipv6Index++
  210. }
  211. _, candidateNet, _ := net.ParseCIDR(newSubnet)
  212. // Ensure no conflicts
  213. if !isConflicting(candidateNet, existingNets) {
  214. _, newSubnetCidr, _ := net.ParseCIDR(newSubnet)
  215. h.Interfaces[i].VirtualNATAddr = *newSubnetCidr
  216. } else {
  217. return fmt.Errorf("could not find non-conflicting subnet for %s", iface.Address.String())
  218. }
  219. }
  220. return nil
  221. }
  222. func GetEgressRanges(netID models.NetworkID) (map[string][]string, map[string]struct{}, error) {
  223. resultMap := make(map[string]struct{})
  224. nodeEgressMap := make(map[string][]string)
  225. networkNodes, err := GetNetworkNodes(netID.String())
  226. if err != nil {
  227. return nil, nil, err
  228. }
  229. for _, currentNode := range networkNodes {
  230. if currentNode.Network != netID.String() {
  231. continue
  232. }
  233. if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
  234. if len(currentNode.EgressGatewayRanges) > 0 {
  235. nodeEgressMap[currentNode.ID.String()] = currentNode.EgressGatewayRanges
  236. for _, egressRangeI := range currentNode.EgressGatewayRanges {
  237. resultMap[egressRangeI] = struct{}{}
  238. }
  239. }
  240. }
  241. }
  242. extclients, _ := GetNetworkExtClients(netID.String())
  243. for _, extclient := range extclients {
  244. if len(extclient.ExtraAllowedIPs) > 0 {
  245. nodeEgressMap[extclient.ClientID] = extclient.ExtraAllowedIPs
  246. for _, extraAllowedIP := range extclient.ExtraAllowedIPs {
  247. resultMap[extraAllowedIP] = struct{}{}
  248. }
  249. }
  250. }
  251. return nodeEgressMap, resultMap, nil
  252. }
  253. func sortRouteMetricByAscending(items []models.EgressRangeMetric) []models.EgressRangeMetric {
  254. sort.Slice(items, func(i, j int) bool {
  255. return items[i].RouteMetric < items[j].RouteMetric
  256. })
  257. return items
  258. }
  259. func GetEgressRangesWithMetric(netID models.NetworkID) map[string][]models.EgressRangeMetric {
  260. egressMap := make(map[string][]models.EgressRangeMetric)
  261. networkNodes, err := GetNetworkNodes(netID.String())
  262. if err != nil {
  263. return nil
  264. }
  265. for _, currentNode := range networkNodes {
  266. if currentNode.Network != netID.String() {
  267. continue
  268. }
  269. if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
  270. if len(currentNode.EgressGatewayRequest.RangesWithMetric) > 0 {
  271. for _, egressRangeI := range currentNode.EgressGatewayRequest.RangesWithMetric {
  272. if value, ok := egressMap[egressRangeI.Network]; ok {
  273. value = append(value, egressRangeI)
  274. egressMap[egressRangeI.Network] = value
  275. } else {
  276. egressMap[egressRangeI.Network] = []models.EgressRangeMetric{
  277. egressRangeI,
  278. }
  279. }
  280. }
  281. }
  282. }
  283. }
  284. for key, value := range egressMap {
  285. egressMap[key] = sortRouteMetricByAscending(value)
  286. }
  287. return egressMap
  288. }
  289. // GetAllEgresses - gets all the nodes that are egresses
  290. func GetAllEgresses() ([]models.Node, error) {
  291. nodes, err := GetAllNodes()
  292. if err != nil {
  293. return nil, err
  294. }
  295. egresses := make([]models.Node, 0)
  296. for _, node := range nodes {
  297. if node.IsEgressGateway {
  298. egresses = append(egresses, node)
  299. }
  300. }
  301. return egresses, nil
  302. }
  303. // CreateEgressGateway - creates an egress gateway
  304. func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
  305. node, err := GetNodeByID(gateway.NodeID)
  306. if err != nil {
  307. return models.Node{}, err
  308. }
  309. host, err := GetHost(node.HostID.String())
  310. if err != nil {
  311. return models.Node{}, err
  312. }
  313. if host.OS != "linux" { // support for other OS to be added
  314. return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
  315. }
  316. if host.FirewallInUse == models.FIREWALL_NONE {
  317. return models.Node{}, errors.New("please install iptables or nftables on the device")
  318. }
  319. if len(gateway.RangesWithMetric) == 0 && len(gateway.Ranges) > 0 {
  320. for _, rangeI := range gateway.Ranges {
  321. gateway.RangesWithMetric = append(gateway.RangesWithMetric, models.EgressRangeMetric{
  322. Network: rangeI,
  323. RouteMetric: 256,
  324. })
  325. }
  326. }
  327. for i := len(gateway.Ranges) - 1; i >= 0; i-- {
  328. // check if internet gateway IPv4
  329. if gateway.Ranges[i] == "0.0.0.0/0" || gateway.Ranges[i] == "::/0" {
  330. // remove inet range
  331. gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
  332. continue
  333. }
  334. normalized, err := NormalizeCIDR(gateway.Ranges[i])
  335. if err != nil {
  336. return models.Node{}, err
  337. }
  338. gateway.Ranges[i] = normalized
  339. }
  340. rangesWithMetric := []string{}
  341. for i := len(gateway.RangesWithMetric) - 1; i >= 0; i-- {
  342. if gateway.RangesWithMetric[i].Network == "0.0.0.0/0" || gateway.RangesWithMetric[i].Network == "::/0" {
  343. // remove inet range
  344. gateway.RangesWithMetric = append(gateway.RangesWithMetric[:i], gateway.RangesWithMetric[i+1:]...)
  345. continue
  346. }
  347. normalized, err := NormalizeCIDR(gateway.RangesWithMetric[i].Network)
  348. if err != nil {
  349. return models.Node{}, err
  350. }
  351. gateway.RangesWithMetric[i].Network = normalized
  352. rangesWithMetric = append(rangesWithMetric, gateway.RangesWithMetric[i].Network)
  353. if gateway.RangesWithMetric[i].RouteMetric <= 0 || gateway.RangesWithMetric[i].RouteMetric > 999 {
  354. gateway.RangesWithMetric[i].RouteMetric = 256
  355. }
  356. }
  357. sort.Strings(gateway.Ranges)
  358. sort.Strings(rangesWithMetric)
  359. if !slices.Equal(gateway.Ranges, rangesWithMetric) {
  360. return models.Node{}, errors.New("invalid ranges")
  361. }
  362. if gateway.NatEnabled == "" {
  363. gateway.NatEnabled = "yes"
  364. }
  365. err = ValidateEgressGateway(gateway)
  366. if err != nil {
  367. return models.Node{}, err
  368. }
  369. if gateway.Ranges == nil {
  370. gateway.Ranges = make([]string, 0)
  371. }
  372. node.IsEgressGateway = true
  373. node.EgressGatewayRanges = gateway.Ranges
  374. node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
  375. node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
  376. node.SetLastModified()
  377. if err = UpsertNode(&node); err != nil {
  378. return models.Node{}, err
  379. }
  380. return node, nil
  381. }
  382. // ValidateEgressGateway - validates the egress gateway model
  383. func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
  384. return nil
  385. }
  386. // DeleteEgressGateway - deletes egress from node
  387. func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
  388. node, err := GetNodeByID(nodeid)
  389. if err != nil {
  390. return models.Node{}, err
  391. }
  392. node.IsEgressGateway = false
  393. node.EgressGatewayRanges = []string{}
  394. node.EgressGatewayRequest = models.EgressGatewayRequest{} // remove preserved request as the egress gateway is gone
  395. node.SetLastModified()
  396. if err = UpsertNode(&node); err != nil {
  397. return models.Node{}, err
  398. }
  399. return node, nil
  400. }