From bc2bb1ed9ac977c8ad540d7ccbfff8e9980277cf Mon Sep 17 00:00:00 2001
From: fett0 <fernando.gmaidana@gmail.com>
Date: Wed, 31 Jul 2024 18:21:25 +0000
Subject: OPENVPN: T6555: add server-bridge options in mode server

(cherry picked from commit 4acad3eb8d9be173b76fecafc32b0c70eae9b192)
---
 data/templates/openvpn/server.conf.j2            |  4 +-
 interface-definitions/interfaces_openvpn.xml.in  | 56 ++++++++++++++++++++++++
 smoketest/scripts/cli/test_interfaces_openvpn.py | 55 +++++++++++++++++++++++
 src/conf_mode/interfaces_openvpn.py              | 16 +++++++
 4 files changed, 130 insertions(+), 1 deletion(-)

diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index 6ac525443..b6e90ed18 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -90,7 +90,9 @@ server-ipv6 {{ subnet }}
 {%                 endif %}
 {%             endfor %}
 {%         endif %}
-
+{%         if server.server_bridge is vyos_defined and server.server_bridge.disable is not vyos_defined %}
+server-bridge {{ server.server_bridge.gateway }} {{ server.server_bridge.subnet_mask }} {{ server.server_bridge.start }} {{ server.server_bridge.stop if server.server_bridge.stop is vyos_defined }}
+{%         endif %}
 {%         if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
 ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }} {{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
 {%         endif %}
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in
index 1860523c2..46c22ac44 100644
--- a/interface-definitions/interfaces_openvpn.xml.in
+++ b/interface-definitions/interfaces_openvpn.xml.in
@@ -445,6 +445,62 @@
                   </leafNode>
                 </children>
               </tagNode>
+              <node name="server-bridge">
+                <properties>
+                  <help>Used with TAP device (layer 2)</help>
+                </properties>
+                <children>
+                  #include <include/generic-disable-node.xml.i>
+                  <leafNode name="start">
+                    <properties>
+                      <help>First IP address in the pool</help>
+                      <constraint>
+                        <validator name="ipv4-address"/>
+                      </constraint>
+                      <valueHelp>
+                        <format>ipv4</format>
+                        <description>IPv4 address</description>
+                      </valueHelp>
+                    </properties>
+                  </leafNode>
+                  <leafNode name="stop">
+                    <properties>
+                      <help>Last IP address in the pool</help>
+                      <constraint>
+                        <validator name="ipv4-address"/>
+                      </constraint>
+                      <valueHelp>
+                        <format>ipv4</format>
+                        <description>IPv4 address</description>
+                      </valueHelp>
+                    </properties>
+                  </leafNode>
+                  <leafNode name="subnet-mask">
+                    <properties>
+                      <help>Subnet mask pushed to dynamic clients.</help>
+                      <constraint>
+                        <validator name="ipv4-address"/>
+                      </constraint>
+                      <valueHelp>
+                        <format>ipv4</format>
+                        <description>IPv4 subnet mask</description>
+                      </valueHelp>
+                    </properties>
+                  </leafNode>
+                  <leafNode name="gateway">
+                    <properties>
+                      <help>Gateway IP address</help>
+                      <constraint>
+                        <validator name="ipv4-address"/>
+                      </constraint>
+                      <valueHelp>
+                        <format>ipv4</format>
+                        <description>IPv4 address</description>
+                      </valueHelp>
+                    </properties>
+                  </leafNode>
+                </children>
+              </node>
               <node name="client-ip-pool">
                 <properties>
                   <help>Pool of client IPv4 addresses</help>
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 9ca661e87..53cfa6cf5 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -627,5 +627,60 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
             self.assertNotIn(interface, interfaces())
 
 
+    def test_openvpn_server_server_bridge(self):
+        # Create OpenVPN server interface using server-bridge.
+        # Validate configuration afterwards.
+        br_if = 'br0'
+        vtun_if = 'vtun5010'
+        auth_hash = 'sha256'
+        path = base_path + [vtun_if]
+        start_subnet = "192.168.0.100"
+        stop_subnet = "192.168.0.200"
+        mask_subnet = "255.255.255.0"
+        gw_subnet = "192.168.0.1"
+
+        self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
+        self.cli_set(path + ['device-type', 'tap'])
+        self.cli_set(path + ['encryption', 'data-ciphers', 'aes192'])
+        self.cli_set(path + ['hash', auth_hash])
+        self.cli_set(path + ['mode', 'server'])
+        self.cli_set(path + ['server', 'server-bridge', 'gateway', gw_subnet])
+        self.cli_set(path + ['server', 'server-bridge', 'start', start_subnet])
+        self.cli_set(path + ['server', 'server-bridge', 'stop', stop_subnet])
+        self.cli_set(path + ['server', 'server-bridge', 'subnet-mask', mask_subnet])
+        self.cli_set(path + ['keep-alive', 'failure-count', '5'])
+        self.cli_set(path + ['keep-alive', 'interval', '5'])
+        self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+        self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
+        self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
+
+        self.cli_commit()
+
+
+
+        config_file = f'/run/openvpn/{vtun_if}.conf'
+        config = read_file(config_file)
+        self.assertIn(f'dev {vtun_if}', config)
+        self.assertIn(f'dev-type tap', config)
+        self.assertIn(f'proto udp', config) # default protocol
+        self.assertIn(f'auth {auth_hash}', config)
+        self.assertIn(f'data-ciphers AES-192-CBC', config)
+        self.assertIn(f'mode server', config)
+        self.assertIn(f'server-bridge {gw_subnet} {mask_subnet} {start_subnet} {stop_subnet}', config)
+        elf.assertIn(f'keepalive 5 25', config)
+
+
+
+        # TLS options
+        self.assertIn(f'ca /run/openvpn/{vtun_if}_ca.pem', config)
+        self.assertIn(f'cert /run/openvpn/{vtun_if}_cert.pem', config)
+        self.assertIn(f'key /run/openvpn/{vtun_if}_cert.key', config)
+        self.assertIn(f'dh /run/openvpn/{vtun_if}_dh.pem', config)
+
+        # check that no interface remained after deleting them
+        self.cli_delete((['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
+        self.cli_delete(base_path)
+        self.cli_commit()
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index 320ab7b7b..fcbba30ce 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -378,6 +378,22 @@ def verify(openvpn):
                 if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1):
                     raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
 
+        if dict_search('server.server_bridge', openvpn):
+            # check if server-bridge is a tap interfaces
+            if not openvpn['device_type'] == 'tap' and dict_search('server.server_bridge', openvpn):
+               raise ConfigError('Must specify "device-type tap" with server-bridge mode')
+            elif not (dict_search('server.server_bridge.start', openvpn) and dict_search('server.server_bridge.stop', openvpn)):
+                raise ConfigError('Server server-bridge requires both start and stop addresses')
+            else:
+                v4PoolStart = IPv4Address(dict_search('server.server_bridge.start', openvpn))
+                v4PoolStop = IPv4Address(dict_search('server.server_bridge.stop', openvpn))
+                if v4PoolStart > v4PoolStop:
+                    raise ConfigError(f'Server server-bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+
+                v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
+                if v4PoolSize >= 65536:
+                    raise ConfigError(f'Server server_bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
+
         if dict_search('server.client_ip_pool', openvpn):
             if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
                 raise ConfigError('Server client-ip-pool requires both start and stop addresses')
-- 
cgit v1.2.3


From f94d7f081074f4debef2eb25855412c26a061ddf Mon Sep 17 00:00:00 2001
From: fett0 <fernando.gmaidana@gmail.com>
Date: Fri, 2 Aug 2024 14:10:51 +0000
Subject: OPENVPN: T6555: fix name to bridge

(cherry picked from commit d5ae708581d453e2205ad4cf8576503f42e262b6)
---
 data/templates/openvpn/server.conf.j2            |  4 ++--
 interface-definitions/interfaces_openvpn.xml.in  |  2 +-
 smoketest/scripts/cli/test_interfaces_openvpn.py | 10 +++++-----
 src/conf_mode/interfaces_openvpn.py              | 18 +++++++++---------
 4 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index b6e90ed18..bc3a6127f 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -90,8 +90,8 @@ server-ipv6 {{ subnet }}
 {%                 endif %}
 {%             endfor %}
 {%         endif %}
-{%         if server.server_bridge is vyos_defined and server.server_bridge.disable is not vyos_defined %}
-server-bridge {{ server.server_bridge.gateway }} {{ server.server_bridge.subnet_mask }} {{ server.server_bridge.start }} {{ server.server_bridge.stop if server.server_bridge.stop is vyos_defined }}
+{%         if server.bridge is vyos_defined and server.bridge.disable is not vyos_defined %}
+server-bridge {{ server.bridge.gateway }} {{ server.bridge.subnet_mask }} {{ server.bridge.start }} {{ server.bridge.stop if server.bridge.stop is vyos_defined }}
 {%         endif %}
 {%         if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
 ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }} {{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in
index 46c22ac44..2d880feef 100644
--- a/interface-definitions/interfaces_openvpn.xml.in
+++ b/interface-definitions/interfaces_openvpn.xml.in
@@ -445,7 +445,7 @@
                   </leafNode>
                 </children>
               </tagNode>
-              <node name="server-bridge">
+              <node name="bridge">
                 <properties>
                   <help>Used with TAP device (layer 2)</help>
                 </properties>
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 53cfa6cf5..3f05dd81c 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -628,7 +628,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
 
 
     def test_openvpn_server_server_bridge(self):
-        # Create OpenVPN server interface using server-bridge.
+        # Create OpenVPN server interface using bridge.
         # Validate configuration afterwards.
         br_if = 'br0'
         vtun_if = 'vtun5010'
@@ -644,10 +644,10 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
         self.cli_set(path + ['encryption', 'data-ciphers', 'aes192'])
         self.cli_set(path + ['hash', auth_hash])
         self.cli_set(path + ['mode', 'server'])
-        self.cli_set(path + ['server', 'server-bridge', 'gateway', gw_subnet])
-        self.cli_set(path + ['server', 'server-bridge', 'start', start_subnet])
-        self.cli_set(path + ['server', 'server-bridge', 'stop', stop_subnet])
-        self.cli_set(path + ['server', 'server-bridge', 'subnet-mask', mask_subnet])
+        self.cli_set(path + ['server', 'bridge', 'gateway', gw_subnet])
+        self.cli_set(path + ['server', 'bridge', 'start', start_subnet])
+        self.cli_set(path + ['server', 'bridge', 'stop', stop_subnet])
+        self.cli_set(path + ['server', 'bridge', 'subnet-mask', mask_subnet])
         self.cli_set(path + ['keep-alive', 'failure-count', '5'])
         self.cli_set(path + ['keep-alive', 'interval', '5'])
         self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index fcbba30ce..fe6a8fb63 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -378,21 +378,21 @@ def verify(openvpn):
                 if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1):
                     raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
 
-        if dict_search('server.server_bridge', openvpn):
+        if dict_search('server.bridge', openvpn):
             # check if server-bridge is a tap interfaces
-            if not openvpn['device_type'] == 'tap' and dict_search('server.server_bridge', openvpn):
-               raise ConfigError('Must specify "device-type tap" with server-bridge mode')
-            elif not (dict_search('server.server_bridge.start', openvpn) and dict_search('server.server_bridge.stop', openvpn)):
-                raise ConfigError('Server server-bridge requires both start and stop addresses')
+            if not openvpn['device_type'] == 'tap' and dict_search('server.bridge', openvpn):
+               raise ConfigError('Must specify "device-type tap" with server bridge mode')
+            elif not (dict_search('server.bridge.start', openvpn) and dict_search('server.bridge.stop', openvpn)):
+                raise ConfigError('Server server bridge requires both start and stop addresses')
             else:
-                v4PoolStart = IPv4Address(dict_search('server.server_bridge.start', openvpn))
-                v4PoolStop = IPv4Address(dict_search('server.server_bridge.stop', openvpn))
+                v4PoolStart = IPv4Address(dict_search('server.bridge.start', openvpn))
+                v4PoolStop = IPv4Address(dict_search('server.bridge.stop', openvpn))
                 if v4PoolStart > v4PoolStop:
-                    raise ConfigError(f'Server server-bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+                    raise ConfigError(f'Server server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
 
                 v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
                 if v4PoolSize >= 65536:
-                    raise ConfigError(f'Server server_bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
+                    raise ConfigError(f'Server bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
 
         if dict_search('server.client_ip_pool', openvpn):
             if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
-- 
cgit v1.2.3


From 48c4e500a1452a6df8c718209b6c93a83dbbd04e Mon Sep 17 00:00:00 2001
From: fett0 <fernando.gmaidana@gmail.com>
Date: Fri, 2 Aug 2024 14:27:56 +0000
Subject: OPENVPN: T6555: fix name to bridge

(cherry picked from commit 0162a27952d2166583a9e6aee2cd77b9c693062b)
---
 src/conf_mode/interfaces_openvpn.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index fe6a8fb63..d2665d9e5 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -379,16 +379,16 @@ def verify(openvpn):
                     raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
 
         if dict_search('server.bridge', openvpn):
-            # check if server-bridge is a tap interfaces
+            # check if server bridge is a tap interfaces
             if not openvpn['device_type'] == 'tap' and dict_search('server.bridge', openvpn):
                raise ConfigError('Must specify "device-type tap" with server bridge mode')
             elif not (dict_search('server.bridge.start', openvpn) and dict_search('server.bridge.stop', openvpn)):
-                raise ConfigError('Server server bridge requires both start and stop addresses')
+                raise ConfigError('Server bridge requires both start and stop addresses')
             else:
                 v4PoolStart = IPv4Address(dict_search('server.bridge.start', openvpn))
                 v4PoolStop = IPv4Address(dict_search('server.bridge.stop', openvpn))
                 if v4PoolStart > v4PoolStop:
-                    raise ConfigError(f'Server server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+                    raise ConfigError(f'Server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
 
                 v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
                 if v4PoolSize >= 65536:
-- 
cgit v1.2.3


From a075d17b37161d02e7e168f3cc6bba94d96cc256 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Mon, 5 Aug 2024 05:45:00 +0000
Subject: smoketest: T6555: openvpn: SyntaxError: '(' was never closed

(cherry picked from commit 2fd817e51532c6428c95704233e62585e76b2ad8)
---
 smoketest/scripts/cli/test_interfaces_openvpn.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 3f05dd81c..48ea318ef 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -678,7 +678,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
         self.assertIn(f'dh /run/openvpn/{vtun_if}_dh.pem', config)
 
         # check that no interface remained after deleting them
-        self.cli_delete((['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
+        self.cli_delete(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
         self.cli_delete(base_path)
         self.cli_commit()
 
-- 
cgit v1.2.3


From 01da8d9cd91b9c35dcbbc9160f6377655d5c8983 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Mon, 5 Aug 2024 20:22:25 +0200
Subject: smoketest: T6555: openvpn: NameError: name 'elf' is not defined

(cherry picked from commit 9bd2c196fe238a38f4fd0977efd1727333e7770e)
---
 smoketest/scripts/cli/test_interfaces_openvpn.py | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 48ea318ef..d24ce831c 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -656,8 +656,6 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
 
         self.cli_commit()
 
-
-
         config_file = f'/run/openvpn/{vtun_if}.conf'
         config = read_file(config_file)
         self.assertIn(f'dev {vtun_if}', config)
@@ -667,9 +665,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
         self.assertIn(f'data-ciphers AES-192-CBC', config)
         self.assertIn(f'mode server', config)
         self.assertIn(f'server-bridge {gw_subnet} {mask_subnet} {start_subnet} {stop_subnet}', config)
-        elf.assertIn(f'keepalive 5 25', config)
-
-
+        self.assertIn(f'keepalive 5 25', config)
 
         # TLS options
         self.assertIn(f'ca /run/openvpn/{vtun_if}_ca.pem', config)
-- 
cgit v1.2.3