From 76109e22d03a18286fc5d4b2b5ed879030f9222c Mon Sep 17 00:00:00 2001
From: Nicolás Fort <95703796+nicolas-fort@users.noreply.github.com>
Date: Thu, 4 Jan 2024 12:49:39 -0300
Subject: T5159: nat: add option to map network and ports. Feature used for
 large deployments in cgnat. (#2694)

(cherry picked from commit 3fc76505d0642c32a3eae9c0ce6ab3dd2ec32dbd)
---
 python/vyos/nat.py                | 10 ++++++++--
 smoketest/scripts/cli/test_nat.py | 20 ++++++++++++++++++++
 src/conf_mode/nat.py              |  5 -----
 3 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 392d38772..7215aac88 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -89,7 +89,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
             if addr and is_ip_network(addr):
                 if not ipv6:
                     map_addr =  dict_search_args(rule_conf, nat_type, 'address')
-                    translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
+                    if port:
+                        translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} . {port} }}')
+                    else:
+                        translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
                     ignore_type_addr = True
                 else:
                     translation_output.append(f'prefix to {addr}')
@@ -112,7 +115,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
         if port_mapping and port_mapping != 'none':
             options.append(port_mapping)
 
-        translation_str = " ".join(translation_output) + (f':{port}' if port else '')
+        if ((not addr) or (addr and not is_ip_network(addr))) and port:
+            translation_str = " ".join(translation_output) + (f':{port}')
+        else:
+            translation_str = " ".join(translation_output)
 
         if options:
             translation_str += f' {",".join(options)}'
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 682fc141d..1e6435df8 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -292,5 +292,25 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
 
         self.verify_nftables(nftables_search, 'ip vyos_nat')
 
+    def test_snat_net_port_map(self):
+        self.cli_set(src_path + ['rule', '10', 'protocol', 'tcp_udp'])
+        self.cli_set(src_path + ['rule', '10', 'source', 'address', '100.64.0.0/25'])
+        self.cli_set(src_path + ['rule', '10', 'translation', 'address', '203.0.113.0/25'])
+        self.cli_set(src_path + ['rule', '10', 'translation', 'port', '1025-3072'])
+
+        self.cli_set(src_path + ['rule', '20', 'protocol', 'tcp_udp'])
+        self.cli_set(src_path + ['rule', '20', 'source', 'address', '100.64.0.128/25'])
+        self.cli_set(src_path + ['rule', '20', 'translation', 'address', '203.0.113.128/25'])
+        self.cli_set(src_path + ['rule', '20', 'translation', 'port', '1025-3072'])
+
+        self.cli_commit()
+
+        nftables_search = [
+            ['meta l4proto { tcp, udp }', 'snat ip prefix to ip saddr map { 100.64.0.0/25 : 203.0.113.0/25 . 1025-3072 }', 'comment "SRC-NAT-10"'],
+            ['meta l4proto { tcp, udp }', 'snat ip prefix to ip saddr map { 100.64.0.128/25 : 203.0.113.128/25 . 1025-3072 }', 'comment "SRC-NAT-20"']
+        ]
+
+        self.verify_nftables(nftables_search, 'ip vyos_nat')
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 7ab4c8648..ffd4a33e7 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -78,11 +78,6 @@ def verify_rule(config, err_msg, groups_dict):
             raise ConfigError(f'{err_msg} ports can only be specified when '\
                               'protocol is either tcp, udp or tcp_udp!')
 
-        if is_ip_network(dict_search('translation.address', config)):
-            raise ConfigError(f'{err_msg} cannot use ports with an IPv4 network as '\
-                             'translation address as it statically maps a whole network '\
-                             'of addresses onto another network of addresses!')
-
     for side in ['destination', 'source']:
         if side in config:
             side_conf = config[side]
-- 
cgit v1.2.3