Skip to content

Commit dc1da7c

Browse files
authored
Merge pull request #4037 from vyos/mergify/bp/sagitta/pr-3920
OPENVPN: T6555: add server-bridge options in mode server (backport #3920)
2 parents 92504ce + 8461eea commit dc1da7c

File tree

4 files changed

+126
-1
lines changed

4 files changed

+126
-1
lines changed

data/templates/openvpn/server.conf.j2

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ server-ipv6 {{ subnet }}
9090
{% endif %}
9191
{% endfor %}
9292
{% endif %}
93-
93+
{% if server.bridge is vyos_defined and server.bridge.disable is not vyos_defined %}
94+
server-bridge {{ server.bridge.gateway }} {{ server.bridge.subnet_mask }} {{ server.bridge.start }} {{ server.bridge.stop if server.bridge.stop is vyos_defined }}
95+
{% endif %}
9496
{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
9597
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 }}
9698
{% endif %}

interface-definitions/interfaces_openvpn.xml.in

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,62 @@
461461
</leafNode>
462462
</children>
463463
</tagNode>
464+
<node name="bridge">
465+
<properties>
466+
<help>Used with TAP device (layer 2)</help>
467+
</properties>
468+
<children>
469+
#include <include/generic-disable-node.xml.i>
470+
<leafNode name="start">
471+
<properties>
472+
<help>First IP address in the pool</help>
473+
<constraint>
474+
<validator name="ipv4-address"/>
475+
</constraint>
476+
<valueHelp>
477+
<format>ipv4</format>
478+
<description>IPv4 address</description>
479+
</valueHelp>
480+
</properties>
481+
</leafNode>
482+
<leafNode name="stop">
483+
<properties>
484+
<help>Last IP address in the pool</help>
485+
<constraint>
486+
<validator name="ipv4-address"/>
487+
</constraint>
488+
<valueHelp>
489+
<format>ipv4</format>
490+
<description>IPv4 address</description>
491+
</valueHelp>
492+
</properties>
493+
</leafNode>
494+
<leafNode name="subnet-mask">
495+
<properties>
496+
<help>Subnet mask pushed to dynamic clients.</help>
497+
<constraint>
498+
<validator name="ipv4-address"/>
499+
</constraint>
500+
<valueHelp>
501+
<format>ipv4</format>
502+
<description>IPv4 subnet mask</description>
503+
</valueHelp>
504+
</properties>
505+
</leafNode>
506+
<leafNode name="gateway">
507+
<properties>
508+
<help>Gateway IP address</help>
509+
<constraint>
510+
<validator name="ipv4-address"/>
511+
</constraint>
512+
<valueHelp>
513+
<format>ipv4</format>
514+
<description>IPv4 address</description>
515+
</valueHelp>
516+
</properties>
517+
</leafNode>
518+
</children>
519+
</node>
464520
<node name="client-ip-pool">
465521
<properties>
466522
<help>Pool of client IPv4 addresses</help>

smoketest/scripts/cli/test_interfaces_openvpn.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,5 +611,56 @@ def test_openvpn_site2site_interfaces_tun(self):
611611
self.assertNotIn(interface, interfaces())
612612

613613

614+
def test_openvpn_server_server_bridge(self):
615+
# Create OpenVPN server interface using bridge.
616+
# Validate configuration afterwards.
617+
br_if = 'br0'
618+
vtun_if = 'vtun5010'
619+
auth_hash = 'sha256'
620+
path = base_path + [vtun_if]
621+
start_subnet = "192.168.0.100"
622+
stop_subnet = "192.168.0.200"
623+
mask_subnet = "255.255.255.0"
624+
gw_subnet = "192.168.0.1"
625+
626+
self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
627+
self.cli_set(path + ['device-type', 'tap'])
628+
self.cli_set(path + ['encryption', 'data-ciphers', 'aes192'])
629+
self.cli_set(path + ['hash', auth_hash])
630+
self.cli_set(path + ['mode', 'server'])
631+
self.cli_set(path + ['server', 'bridge', 'gateway', gw_subnet])
632+
self.cli_set(path + ['server', 'bridge', 'start', start_subnet])
633+
self.cli_set(path + ['server', 'bridge', 'stop', stop_subnet])
634+
self.cli_set(path + ['server', 'bridge', 'subnet-mask', mask_subnet])
635+
self.cli_set(path + ['keep-alive', 'failure-count', '5'])
636+
self.cli_set(path + ['keep-alive', 'interval', '5'])
637+
self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
638+
self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
639+
self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
640+
641+
self.cli_commit()
642+
643+
config_file = f'/run/openvpn/{vtun_if}.conf'
644+
config = read_file(config_file)
645+
self.assertIn(f'dev {vtun_if}', config)
646+
self.assertIn(f'dev-type tap', config)
647+
self.assertIn(f'proto udp', config) # default protocol
648+
self.assertIn(f'auth {auth_hash}', config)
649+
self.assertIn(f'data-ciphers AES-192-CBC', config)
650+
self.assertIn(f'mode server', config)
651+
self.assertIn(f'server-bridge {gw_subnet} {mask_subnet} {start_subnet} {stop_subnet}', config)
652+
self.assertIn(f'keepalive 5 25', config)
653+
654+
# TLS options
655+
self.assertIn(f'ca /run/openvpn/{vtun_if}_ca.pem', config)
656+
self.assertIn(f'cert /run/openvpn/{vtun_if}_cert.pem', config)
657+
self.assertIn(f'key /run/openvpn/{vtun_if}_cert.key', config)
658+
self.assertIn(f'dh /run/openvpn/{vtun_if}_dh.pem', config)
659+
660+
# check that no interface remained after deleting them
661+
self.cli_delete(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
662+
self.cli_delete(base_path)
663+
self.cli_commit()
664+
614665
if __name__ == '__main__':
615666
unittest.main(verbosity=2)

src/conf_mode/interfaces_openvpn.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,22 @@ def verify(openvpn):
378378
if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1):
379379
raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
380380

381+
if dict_search('server.bridge', openvpn):
382+
# check if server bridge is a tap interfaces
383+
if not openvpn['device_type'] == 'tap' and dict_search('server.bridge', openvpn):
384+
raise ConfigError('Must specify "device-type tap" with server bridge mode')
385+
elif not (dict_search('server.bridge.start', openvpn) and dict_search('server.bridge.stop', openvpn)):
386+
raise ConfigError('Server bridge requires both start and stop addresses')
387+
else:
388+
v4PoolStart = IPv4Address(dict_search('server.bridge.start', openvpn))
389+
v4PoolStop = IPv4Address(dict_search('server.bridge.stop', openvpn))
390+
if v4PoolStart > v4PoolStop:
391+
raise ConfigError(f'Server bridge start address {v4PoolStart} is larger than stop address {v4PoolStop}')
392+
393+
v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
394+
if v4PoolSize >= 65536:
395+
raise ConfigError(f'Server bridge is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
396+
381397
if dict_search('server.client_ip_pool', openvpn):
382398
if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
383399
raise ConfigError('Server client-ip-pool requires both start and stop addresses')

0 commit comments

Comments
 (0)