Browse Source

Merge pull request #1509 from gravitl/feature_v0.15.1_ipv6_egress

IPv6 support for gateways
Alex Feiszli 2 years ago
parent
commit
3ead2b5469
4 changed files with 132 additions and 53 deletions
  1. 104 45
      logic/gateway.go
  2. 17 0
      logic/util.go
  3. 4 0
      models/node.go
  4. 7 8
      models/structs.go

+ 104 - 45
logic/gateway.go

@@ -37,22 +37,23 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	node.EgressGatewayRequest = gateway // store entire request for use when preserving the egress gateway
 	postUpCmd := ""
 	postDownCmd := ""
+	ipv4, ipv6 := getNetworkProtocols(gateway.Ranges)
 	logger.Log(3, "creating egress gateway firewall in use is '", node.FirewallInUse, "'")
 	if node.OS == "linux" {
 		switch node.FirewallInUse {
 		case models.FIREWALL_NFTABLES:
 			// nftables only supported on Linux
-			// assumes chains eg FORWARD and POSTROUTING already exist
+			// assumes chains eg FORWARD and postrouting already exist
 			logger.Log(3, "creating egress gateway nftables is present")
 			// down commands don't remove as removal of the rules leaves an empty chain while
 			// removing the chain with rules in it would remove all rules in that section (not safe
 			// if there are remaining rules on the host that need to stay).  In practice the chain is removed
 			// when non-empty even though the removal of a non-empty chain should not be possible per nftables wiki.
-			postUpCmd, postDownCmd = firewallNFTCommandsCreateEgress(node.Interface, gateway.Interface, gateway.Ranges, node.EgressGatewayNatEnabled)
+			postUpCmd, postDownCmd = firewallNFTCommandsCreateEgress(node.Interface, gateway.Interface, gateway.Ranges, node.EgressGatewayNatEnabled, ipv4, ipv6)
 
 		default: // iptables assumed
 			logger.Log(3, "creating egress gateway nftables is not present")
-			postUpCmd, postDownCmd = firewallIPTablesCommandsCreateEgress(node.Interface, gateway.Interface, node.EgressGatewayNatEnabled)
+			postUpCmd, postDownCmd = firewallIPTablesCommandsCreateEgress(node.Interface, gateway.Interface, node.EgressGatewayNatEnabled, ipv4, ipv6)
 		}
 	}
 	if node.OS == "freebsd" {
@@ -129,7 +130,10 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 	// needed in case we don't preserve a gateway (i.e., no ingress to preserve)
 	node.PostUp = ""
 	node.PostDown = ""
-
+	cidrs := []string{}
+	cidrs = append(cidrs, node.IngressGatewayRange)
+	cidrs = append(cidrs, node.IngressGatewayRange6)
+	ipv4, ipv6 := getNetworkProtocols(cidrs)
 	logger.Log(3, "deleting egress gateway firewall in use is '", node.FirewallInUse, "'")
 	if node.IsIngressGateway == "yes" { // check if node is still an ingress gateway before completely deleting postdown/up rules
 		// still have an ingress gateway so preserve it
@@ -137,12 +141,12 @@ func DeleteEgressGateway(network, nodeid string) (models.Node, error) {
 			switch node.FirewallInUse {
 			case models.FIREWALL_NFTABLES:
 				// nftables only supported on Linux
-				// assumes chains eg FORWARD and POSTROUTING already exist
+				// assumes chains eg FORWARD and postrouting already exist
 				logger.Log(3, "deleting egress gateway nftables is present")
 				node.PostUp, node.PostDown = firewallNFTCommandsCreateIngress(node.Interface)
 			default:
 				logger.Log(3, "deleting egress gateway nftables is not present")
-				node.PostUp, node.PostDown = firewallIPTablesCommandsCreateIngress(node.Interface)
+				node.PostUp, node.PostDown = firewallIPTablesCommandsCreateIngress(node.Interface, ipv4, ipv6)
 			}
 		}
 		// no need to preserve ingress gateway on FreeBSD as ingress is not supported on that OS
@@ -180,17 +184,22 @@ func CreateIngressGateway(netid string, nodeid string) (models.Node, error) {
 		return models.Node{}, err
 	}
 	node.IsIngressGateway = "yes"
+	cidrs := []string{}
+	cidrs = append(cidrs, network.AddressRange)
+	cidrs = append(cidrs, network.AddressRange6)
 	node.IngressGatewayRange = network.AddressRange
+	node.IngressGatewayRange6 = network.AddressRange6
+	ipv4, ipv6 := getNetworkProtocols(cidrs)
 	logger.Log(3, "creating ingress gateway firewall in use is '", node.FirewallInUse, "'")
 	switch node.FirewallInUse {
 	case models.FIREWALL_NFTABLES:
 		// nftables only supported on Linux
-		// assumes chains eg FORWARD and POSTROUTING already exist
+		// assumes chains eg FORWARD and postrouting already exist
 		logger.Log(3, "creating ingress gateway nftables is present")
 		postUpCmd, postDownCmd = firewallNFTCommandsCreateIngress(node.Interface)
 	default:
 		logger.Log(3, "creating ingress gateway using nftables is not present")
-		postUpCmd, postDownCmd = firewallIPTablesCommandsCreateIngress(node.Interface)
+		postUpCmd, postDownCmd = firewallIPTablesCommandsCreateIngress(node.Interface, ipv4, ipv6)
 	}
 
 	if node.PostUp != "" {
@@ -292,8 +301,8 @@ func firewallNFTCommandsCreateIngress(networkInterface string) (string, string)
 	postUp += "nft add rule ip filter FORWARD iifname " + networkInterface + " counter accept ; "
 	postUp += "nft add rule ip filter FORWARD oifname " + networkInterface + " counter accept ; "
 	postUp += "nft add table nat ; "
-	postUp += "nft add chain nat POSTROUTING ; "
-	postUp += "nft add rule ip nat POSTROUTING oifname " + networkInterface + " counter masquerade"
+	postUp += "nft add chain nat postrouting ; "
+	postUp += "nft add rule ip nat postrouting oifname " + networkInterface + " counter masquerade"
 
 	// doesn't remove potentially empty tables or chains
 	postDown := "nft flush table filter ; "
@@ -303,61 +312,111 @@ func firewallNFTCommandsCreateIngress(networkInterface string) (string, string)
 }
 
 // firewallNFTCommandsCreateEgress - used to centralize firewall command maintenance for creating an egress gateway using the nftables firewall.
-func firewallNFTCommandsCreateEgress(networkInterface string, gatewayInterface string, gatewayranges []string, egressNatEnabled string) (string, string) {
+func firewallNFTCommandsCreateEgress(networkInterface string, gatewayInterface string, gatewayranges []string, egressNatEnabled string, ipv4, ipv6 bool) (string, string) {
 	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
-	postUp := "nft add table ip filter ; "
-	postUp += "nft add chain ip filter forward ; "
-	postUp += "nft add rule filter forward ct state related,established accept ; "
-	postUp += "nft add rule ip filter forward iifname " + networkInterface + " accept ; "
-	postUp += "nft add rule ip filter forward oifname " + networkInterface + " accept ; "
-	postUp += "nft add table nat ; "
-	postUp += "nft 'add chain ip nat prerouting { type nat hook prerouting priority 0 ;}' ; "
-	postUp += "nft 'add chain ip nat postrouting { type nat hook postrouting priority 0 ;}' ; "
-	for _, networkCIDR := range gatewayranges {
-		postUp += "nft add rule nat postrouting iifname " + networkInterface + " oifname " + gatewayInterface + " ip saddr " + networkCIDR + " masquerade ; "
+	postUp := ""
+	postDown := ""
+	if ipv4 {
+		postUp += "nft add table ip filter ; "
+		postUp += "nft add chain ip filter forward ; "
+		postUp += "nft add rule filter forward ct state related,established accept ; "
+		postUp += "nft add rule ip filter forward iifname " + networkInterface + " accept ; "
+		postUp += "nft add rule ip filter forward oifname " + networkInterface + " accept ; "
+		postUp += "nft add table nat ; "
+		postUp += "nft 'add chain ip nat prerouting { type nat hook prerouting priority 0 ;}' ; "
+		postUp += "nft 'add chain ip nat postrouting { type nat hook postrouting priority 0 ;}' ; "
+		for _, networkCIDR := range gatewayranges {
+			postUp += "nft add rule nat postrouting iifname " + networkInterface + " oifname " + gatewayInterface + " ip saddr " + networkCIDR + " masquerade ; "
+		}
+
+		postDown += "nft flush table filter ; "
+
+		if egressNatEnabled == "yes" {
+			postUp += "nft add table nat ; "
+			postUp += "nft add chain nat postrouting ; "
+			postUp += "nft add rule ip nat postrouting oifname " + gatewayInterface + " counter masquerade ; "
+
+			postDown += "nft flush table nat ; "
+		}
 	}
+	if ipv6 {
+		postUp += "nft add table ip6 filter ; "
+		postUp += "nft add chain ip6 filter forward ; "
+		postUp += "nft add rule ip6 filter forward ct state related,established accept ; "
+		postUp += "nft add rule ip6 filter forward iifname " + networkInterface + " accept ; "
+		postUp += "nft add rule ip6 filter forward oifname " + networkInterface + " accept ; "
 
-	postDown := "nft flush table filter ; "
+		postDown += "nft flush table ip6 filter ; "
 
-	if egressNatEnabled == "yes" {
-		postUp += "nft add table nat ; "
-		postUp += "nft add chain nat POSTROUTING ; "
-		postUp += "nft add rule ip nat POSTROUTING oifname " + gatewayInterface + " counter masquerade ; "
+		if egressNatEnabled == "yes" {
+			postUp += "nft add table ip6 nat ; "
+			postUp += "nft 'add chain ip6 nat prerouting { type nat hook prerouting priority 0 ;}' ; "
+			postUp += "nft 'add chain ip6 nat postrouting { type nat hook postrouting priority 0 ;}' ; "
+			postUp += "nft add rule ip6 nat postrouting oifname " + gatewayInterface + " masquerade ; "
 
-		postDown += "nft flush table nat ; "
+			postDown += "nft flush table ip6 nat ; "
+		}
 	}
 
 	return postUp, postDown
 }
 
 // firewallIPTablesCommandsCreateIngress - used to centralize firewall command maintenance for creating an ingress gateway using the iptables firewall.
-func firewallIPTablesCommandsCreateIngress(networkInterface string) (string, string) {
-	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
-	postUp := "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
-	postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT ; "
-	postUp += "iptables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE"
+func firewallIPTablesCommandsCreateIngress(networkInterface string, ipv4, ipv6 bool) (string, string) {
+	postUp := ""
+	postDown := ""
+	if ipv4 {
+		// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+		postUp += "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT ; "
+		postUp += "iptables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE ; "
 
-	// doesn't remove potentially empty tables or chains
-	postDown := "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
-	postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
-	postDown += "iptables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE"
+		// doesn't remove potentially empty tables or chains
+		postDown += "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
+		postDown += "iptables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE ; "
+	}
+	if ipv6 {
+		// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
+		postUp += "ip6tables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postUp += "ip6tables -A FORWARD -o " + networkInterface + " -j ACCEPT ; "
+		postUp += "ip6tables -t nat -A POSTROUTING -o " + networkInterface + " -j MASQUERADE"
 
+		// doesn't remove potentially empty tables or chains
+		postDown += "ip6tables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postDown += "ip6tables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
+		postDown += "ip6tables -t nat -D POSTROUTING -o " + networkInterface + " -j MASQUERADE"
+	}
 	return postUp, postDown
 }
 
 // firewallIPTablesCommandsCreateEgress - used to centralize firewall command maintenance for creating an egress gateway using the iptables firewall.
-func firewallIPTablesCommandsCreateEgress(networkInterface string, gatewayInterface string, egressNatEnabled string) (string, string) {
+func firewallIPTablesCommandsCreateEgress(networkInterface string, gatewayInterface string, egressNatEnabled string, ipv4, ipv6 bool) (string, string) {
 	// spacing around ; is important for later parsing of postup/postdown in wireguard/common.go
-	postUp := "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
-	postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT"
-	postDown := "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
-	postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT"
-
-	if egressNatEnabled == "yes" {
-		postUp += " ; iptables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
-		postDown += " ; iptables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
+	postUp := ""
+	postDown := ""
+	if ipv4 {
+		postUp += "iptables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postUp += "iptables -A FORWARD -o " + networkInterface + " -j ACCEPT"
+		postDown += "iptables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postDown += "iptables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
+
+		if egressNatEnabled == "yes" {
+			postUp += " ; iptables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; "
+			postDown += " ; iptables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE ; "
+		}
 	}
+	if ipv6 {
+		postUp += "ip6tables -A FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postUp += "ip6tables -A FORWARD -o " + networkInterface + " -j ACCEPT ; "
+		postDown += "ip6tables -D FORWARD -i " + networkInterface + " -j ACCEPT ; "
+		postDown += "ip6tables -D FORWARD -o " + networkInterface + " -j ACCEPT ; "
 
+		if egressNatEnabled == "yes" {
+			postUp += " ; ip6tables -t nat -A POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
+			postDown += " ; ip6tables -t nat -D POSTROUTING -o " + gatewayInterface + " -j MASQUERADE"
+		}
+	}
 	return postUp, postDown
 
 }

+ 17 - 0
logic/util.go

@@ -169,3 +169,20 @@ func ShouldPublishPeerPorts(serverNode *models.Node) bool {
 	}
 	return false
 }
+
+func getNetworkProtocols(cidrs []string) (bool, bool) {
+	ipv4 := false
+	ipv6 := false
+	for _, cidr := range cidrs {
+		ip, _, err := net.ParseCIDR(cidr)
+		if err != nil {
+			continue
+		}
+		if ip.To4() == nil {
+			ipv6 = true
+		} else {
+			ipv4 = true
+		}
+	}
+	return ipv4, ipv6
+}

+ 4 - 0
models/node.go

@@ -77,6 +77,7 @@ type Node struct {
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	RelayAddrs              []string             `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
+	IngressGatewayRange6    string               `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
 	// IsStatic - refers to if the Endpoint is set manually or dynamically
 	IsStatic        string      `json:"isstatic" bson:"isstatic" yaml:"isstatic" validate:"checkyesorno"`
 	UDPHolePunch    string      `json:"udpholepunch" bson:"udpholepunch" yaml:"udpholepunch" validate:"checkyesorno"`
@@ -370,6 +371,9 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.IngressGatewayRange == "" {
 		newNode.IngressGatewayRange = currentNode.IngressGatewayRange
 	}
+	if newNode.IngressGatewayRange6 == "" {
+		newNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6
+	}
 	if newNode.IsStatic == "" {
 		newNode.IsStatic = currentNode.IsStatic
 	}

+ 7 - 8
models/structs.go

@@ -151,14 +151,13 @@ type ExtPeersResponse struct {
 
 // EgressGatewayRequest - egress gateway request
 type EgressGatewayRequest struct {
-	NodeID      string   `json:"nodeid" bson:"nodeid"`
-	NetID       string   `json:"netid" bson:"netid"`
-	RangeString string   `json:"rangestring" bson:"rangestring"`
-	NatEnabled  string   `json:"natenabled" bson:"natenabled"`
-	Ranges      []string `json:"ranges" bson:"ranges"`
-	Interface   string   `json:"interface" bson:"interface"`
-	PostUp      string   `json:"postup" bson:"postup"`
-	PostDown    string   `json:"postdown" bson:"postdown"`
+	NodeID     string   `json:"nodeid" bson:"nodeid"`
+	NetID      string   `json:"netid" bson:"netid"`
+	NatEnabled string   `json:"natenabled" bson:"natenabled"`
+	Ranges     []string `json:"ranges" bson:"ranges"`
+	Interface  string   `json:"interface" bson:"interface"`
+	PostUp     string   `json:"postup" bson:"postup"`
+	PostDown   string   `json:"postdown" bson:"postdown"`
 }
 
 // RelayRequest - relay request struct