From 3a5cf74b06cef960e9a701172618c2c366591255 Mon Sep 17 00:00:00 2001
From: Nicolas Fort <nicolasfort1988@gmail.com>
Date: Fri, 8 Apr 2022 14:55:01 +0000
Subject: Firewall: T990: Add snat and dnat connection status on firewall

---
 .../include/firewall/common-rule.xml.i             | 45 ++++++++++++++++++++++
 python/vyos/firewall.py                            |  6 +++
 smoketest/scripts/cli/test_firewall.py             | 39 +++++++++++++++++++
 3 files changed, 90 insertions(+)

diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index cd80b7e28..6e61de848 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -95,6 +95,51 @@
     </constraint>
   </properties>
 </leafNode>
+<node name="ct-status">
+  <properties>
+    <help>Connection status in conntrack</help>
+  </properties>
+  <children>
+    <leafNode name="dnat">
+      <properties>
+        <help>Set when connection needs DNAT in original direction</help>
+        <completionHelp>
+          <list>enable disable</list>
+        </completionHelp>
+        <valueHelp>
+          <format>enable</format>
+          <description>Enable</description>
+        </valueHelp>
+        <valueHelp>
+          <format>disable</format>
+          <description>Disable</description>
+        </valueHelp>
+        <constraint>
+          <regex>^(enable|disable)$</regex>
+        </constraint>
+      </properties>
+    </leafNode>
+    <leafNode name="snat">
+      <properties>
+        <help>Set when connection needs SNAT in original direction</help>
+        <completionHelp>
+          <list>enable disable</list>
+        </completionHelp>
+        <valueHelp>
+          <format>enable</format>
+          <description>Enable</description>
+        </valueHelp>
+        <valueHelp>
+          <format>disable</format>
+          <description>Disable</description>
+        </valueHelp>
+        <constraint>
+          <regex>^(enable|disable)$</regex>
+        </constraint>
+      </properties>
+    </leafNode>
+  </children>
+</node>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index ff8623592..5e11e4332 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -49,6 +49,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
         if states:
             output.append(f'ct state {{{states}}}')
 
+    if 'ct_status' in rule_conf and rule_conf['ct_status']:
+        status = ",".join([s for s, v in rule_conf['ct_status'].items() if v == 'enable'])
+
+        if status:
+            output.append(f'ct status {{{status}}}')
+
     if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
         proto = rule_conf['protocol']
         operator = ''
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 16b020e07..b914d930a 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -162,6 +162,45 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
                 nftables_output = cmd(f'sudo nft list chain {table} {chain}')
                 self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output)
 
+    def test_state_and_status_rules(self):
+        self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'established', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'state', 'related', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'state', 'invalid', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'ct-status', 'dnat', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'new', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'established', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'ct-status', 'snat', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'ct-status', 'dnat', 'enable'])
+
+        self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+        self.cli_commit()
+
+        nftables_search = [
+            ['iifname "eth0"', 'jump NAME_smoketest'],
+            ['ct state { established, related }', 'return'],
+            ['ct state { invalid }', 'reject'],
+            ['ct state { new }', 'ct status { dnat }', 'return'],
+            ['ct state { established, new }', 'ct status { snat, dnat }', 'return'],
+            ['smoketest default-action', 'drop']
+        ]
+
+        nftables_output = cmd('sudo nft list table ip filter')
+
+        for search in nftables_search:
+            matched = False
+            for line in nftables_output.split("\n"):
+                if all(item in line for item in search):
+                    matched = True
+                    break
+            self.assertTrue(matched, msg=search)
+
     def test_sysfs(self):
         for name, conf in sysfs_config.items():
             paths = glob(conf['sysfs'])
-- 
cgit v1.2.3


From 19d38aa98cd656a2d4c558f6c99635b3d662b9cb Mon Sep 17 00:00:00 2001
From: Nicolas Fort <nicolasfort1988@gmail.com>
Date: Mon, 11 Apr 2022 17:52:30 +0000
Subject: Firewall: T990: Add snat and dst connection status on firewall

---
 .../include/firewall/common-rule.xml.i             | 60 ++++++----------------
 python/vyos/firewall.py                            |  8 ++-
 smoketest/scripts/cli/test_firewall.py             |  7 ++-
 3 files changed, 23 insertions(+), 52 deletions(-)

diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 6e61de848..e74ce4ee4 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -95,51 +95,25 @@
     </constraint>
   </properties>
 </leafNode>
-<node name="ct-status">
+<leafNode name="connection-status">
   <properties>
-    <help>Connection status in conntrack</help>
+    <help>Connection status</help>
+    <completionHelp>
+      <list>dnat snat</list>
+    </completionHelp>
+    <valueHelp>
+      <format>dnat</format>
+      <description>Match connections that are subject to destination NAT</description>
+    </valueHelp>
+    <valueHelp>
+      <format>snat</format>
+      <description>Match connections that are subject to source NAT</description>
+    </valueHelp>
+    <constraint>
+      <regex>^(dnat|snat)$</regex>
+    </constraint>
   </properties>
-  <children>
-    <leafNode name="dnat">
-      <properties>
-        <help>Set when connection needs DNAT in original direction</help>
-        <completionHelp>
-          <list>enable disable</list>
-        </completionHelp>
-        <valueHelp>
-          <format>enable</format>
-          <description>Enable</description>
-        </valueHelp>
-        <valueHelp>
-          <format>disable</format>
-          <description>Disable</description>
-        </valueHelp>
-        <constraint>
-          <regex>^(enable|disable)$</regex>
-        </constraint>
-      </properties>
-    </leafNode>
-    <leafNode name="snat">
-      <properties>
-        <help>Set when connection needs SNAT in original direction</help>
-        <completionHelp>
-          <list>enable disable</list>
-        </completionHelp>
-        <valueHelp>
-          <format>enable</format>
-          <description>Enable</description>
-        </valueHelp>
-        <valueHelp>
-          <format>disable</format>
-          <description>Disable</description>
-        </valueHelp>
-        <constraint>
-          <regex>^(enable|disable)$</regex>
-        </constraint>
-      </properties>
-    </leafNode>
-  </children>
-</node>
+</leafNode>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 5e11e4332..06731dd64 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -49,11 +49,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
         if states:
             output.append(f'ct state {{{states}}}')
 
-    if 'ct_status' in rule_conf and rule_conf['ct_status']:
-        status = ",".join([s for s, v in rule_conf['ct_status'].items() if v == 'enable'])
-
-        if status:
-            output.append(f'ct status {{{status}}}')
+    if 'connection_status' in rule_conf and rule_conf['connection_status']:
+        status = rule_conf['connection_status']
+        output.append(f'ct status {{{status}}}')
 
     if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
         proto = rule_conf['protocol']
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index b914d930a..13bf02cd1 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -171,12 +171,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'state', 'invalid', 'enable'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
-        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'ct-status', 'dnat', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'connection-status', 'dnat'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'new', 'enable'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'established', 'enable'])
-        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'ct-status', 'snat', 'enable'])
-        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'ct-status', 'dnat', 'enable'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'connection-status', 'snat'])
 
         self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
 
@@ -187,7 +186,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
             ['ct state { established, related }', 'return'],
             ['ct state { invalid }', 'reject'],
             ['ct state { new }', 'ct status { dnat }', 'return'],
-            ['ct state { established, new }', 'ct status { snat, dnat }', 'return'],
+            ['ct state { established, new }', 'ct status { snat }', 'return'],
             ['smoketest default-action', 'drop']
         ]
 
-- 
cgit v1.2.3


From 15e55af88e6104608487c1138641fcff54594d89 Mon Sep 17 00:00:00 2001
From: Nicolas Fort <nicolasfort1988@gmail.com>
Date: Sat, 23 Apr 2022 12:05:57 +0000
Subject: Firewall: T990: Modifications for new connection-status cli

---
 .../include/firewall/common-rule.xml.i             | 39 +++++++++++++---------
 python/vyos/firewall.py                            |  7 +++-
 smoketest/scripts/cli/test_firewall.py             |  5 +--
 3 files changed, 32 insertions(+), 19 deletions(-)

diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index e74ce4ee4..85e586e1b 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -95,25 +95,32 @@
     </constraint>
   </properties>
 </leafNode>
-<leafNode name="connection-status">
+<node name="connection-status">
   <properties>
     <help>Connection status</help>
-    <completionHelp>
-      <list>dnat snat</list>
-    </completionHelp>
-    <valueHelp>
-      <format>dnat</format>
-      <description>Match connections that are subject to destination NAT</description>
-    </valueHelp>
-    <valueHelp>
-      <format>snat</format>
-      <description>Match connections that are subject to source NAT</description>
-    </valueHelp>
-    <constraint>
-      <regex>^(dnat|snat)$</regex>
-    </constraint>
   </properties>
-</leafNode>
+  <children>
+    <leafNode name="nat">
+      <properties>
+        <help>NAT connection status</help>
+        <completionHelp>
+          <list>destination source</list>
+        </completionHelp>
+        <valueHelp>
+          <format>destination</format>
+          <description>Match connections that are subject to destination NAT</description>
+        </valueHelp>
+        <valueHelp>
+          <format>source</format>
+          <description>Match connections that are subject to source NAT</description>
+        </valueHelp>
+        <constraint>
+          <regex>^(destination|source)$</regex>
+        </constraint>
+      </properties>
+    </leafNode>
+  </children>
+</node>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 06731dd64..04fd44173 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -51,7 +51,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
 
     if 'connection_status' in rule_conf and rule_conf['connection_status']:
         status = rule_conf['connection_status']
-        output.append(f'ct status {{{status}}}')
+        if status['nat'] == 'destination':
+            nat_status = '{dnat}'
+            output.append(f'ct status {nat_status}')
+        if status['nat'] == 'source':
+            nat_status = '{snat}'
+            output.append(f'ct status {nat_status}')
 
     if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
         proto = rule_conf['protocol']
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 13bf02cd1..f72dfb1f4 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -171,11 +171,12 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'state', 'invalid', 'enable'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'state', 'new', 'enable'])
-        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'connection-status', 'dnat'])
+
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'connection-status', 'nat', 'destination'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'new', 'enable'])
         self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'state', 'established', 'enable'])
-        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'connection-status', 'snat'])
+        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'connection-status', 'nat', 'source'])
 
         self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
 
-- 
cgit v1.2.3