Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/source/backends/openvpn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,17 @@ key name type default allowed values
``dev`` string any non-whitespace
character (max length: 15)
``local`` string any string
``comp_lzo`` string ``adaptive`` ``yes``, ``no`` or
``adaptive``
``comp_lzo`` string ``adaptive`` **DEPRECATED** - ``yes``, ``no`` or
``adaptive``. Use ``compress``
``compress`` string Empty string (for migration),
``lzo``, ``lz4``, ``lz4-v2``,
``stub``, ``stub-v2``
``allow_compression`` string ``asym`` ``asym`` (compression allowed in
one direction), ``no`` (disabled),
``yes`` (allowed both directions)
``push_compress`` string Server-side option to push
compression settings to clients.
Same values as ``compress``
``auth`` string ``SHA1`` see `auth property source
code`_
``cipher`` string ``BF-CBC`` see `cipher property source
Expand Down
49 changes: 48 additions & 1 deletion netjsonconfig/backends/openvpn/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
"RC2-OFB",
"none",
]

compression_algorithms = [
"lzo",
"lz4",
"lz4-v2",
"stub",
"stub-v2",
]

default_cipher = "AES-256-GCM"

base_openvpn_schema = {
Expand Down Expand Up @@ -146,12 +155,50 @@
"comp_lzo": {
"title": "LZO compression",
"description": "Use fast LZO compression; may add up to 1 "
"byte per packet for incompressible data",
"byte per packet for incompressible data. Deprecated in favor of --compress",
"type": "string",
"enum": ["yes", "no", "adaptive"],
"default": "adaptive",
"propertyOrder": 9,
},
"compress": {
"title": "compression algorithm",
"description": "Enable compression on the VPN link. Recommended modern option. "
"Use 'lz4' or 'lz4-v2' for best performance, 'lzo' for compatibility, "
"'stub' for placeholder, 'stub-v2' for placeholder with peer info, "
"or empty to migrate from comp-lzo",
"type": "string",
"enum": [""] + compression_algorithms,
"default": "",
"options": {
"enum_titles": [
"Disabled (migrate from comp-lzo)",
"LZO compression (for compatibility)",
"LZ4 compression",
"LZ4-v2 compression (recommended)",
"Compression framing without actual compression",
"Compression framing v2 without actual compression",
]
},
"propertyOrder": 9.1,
},
"allow_compression": {
"title": "allow compression",
"description": "Control whether compression is allowed. 'asym' allows compression "
"in one direction only (typically for server pushing to clients). 'no' disables "
"all compression. 'yes' allows compression in both directions.",
"type": "string",
"enum": ["asym", "yes", "no"],
"default": "asym",
"options": {
"enum_titles": [
"Asymmetric (one direction only)",
"No compression allowed",
"Yes - allow compression",
]
},
"propertyOrder": 9.2,
},
"auth": {
"title": "auth digest algorithm",
"type": "string",
Expand Down
207 changes: 207 additions & 0 deletions tests/openvpn/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,210 @@ def test_ca_same_file_path_for_same_device(self):
client.render()
except ValidationError:
self.fail("ValidationError raised!")

def test_compression_options(self):
"""Test modern compress option with various algorithms"""
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"cipher": "AES-128-CBC",
"compress": "lz4-v2",
"dev": "tun0",
"dev_type": "tun",
"key": "key.pem",
"mode": "p2p",
"name": "test-compress",
"nobind": True, # ADD THIS
"proto": "udp",
"remote": [{"host": "vpn.example.com", "port": 1194}],
"resolv_retry": "infinite", # ADD THIS
"tls_client": True,
}
]
}
)
expected = """# openvpn config: test-compress

ca ca.pem
cert cert.pem
cipher AES-128-CBC
compress lz4-v2
dev tun0
dev-type tun
key key.pem
mode p2p
nobind
proto udp
remote vpn.example.com 1194
resolv-retry infinite
tls-client
"""
self.assertEqual(c.render(), expected)

def test_allow_compression(self):
"""Test allow-compression option"""
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"dev": "tap0",
"dev_type": "tap",
"dh": "dh.pem",
"key": "key.pem",
"mode": "server",
"name": "test-server",
"proto": "udp",
"tls_server": True,
"allow_compression": "no",
}
]
}
)
expected = """# openvpn config: test-server

allow-compression no
ca ca.pem
cert cert.pem
dev tap0
dev-type tap
dh dh.pem
key key.pem
mode server
proto udp
tls-server
"""
self.assertEqual(c.render(), expected)

def test_push_compress(self):
"""Test push-compress option for server"""
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"dev": "tap0",
"dev_type": "tap",
"dh": "dh.pem",
"key": "key.pem",
"mode": "server",
"name": "test-server",
"proto": "udp",
"server": "10.8.0.0 255.255.0.0",
"tls_server": True,
"push_compress": "lz4",
}
]
}
)
expected = """# openvpn config: test-server

ca ca.pem
cert cert.pem
dev tap0
dev-type tap
dh dh.pem
key key.pem
mode server
proto udp
push-compress lz4
server 10.8.0.0 255.255.0.0
tls-server
"""
self.assertEqual(c.render(), expected)

def test_compress_with_deprecated_comp_lzo(self):
"""Test that both old and new compression options can coexist"""
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"comp_lzo": "adaptive",
"compress": "stub-v2",
"dev": "tun0",
"dev_type": "tun",
"key": "key.pem",
"mode": "p2p",
"name": "test-migration",
"proto": "udp",
"remote": [{"host": "vpn.example.com", "port": 1194}],
"tls_client": True,
}
]
}
)
output = c.render()
self.assertIn("comp-lzo adaptive", output)
self.assertIn("compress stub-v2", output)

def test_compress_algorithms(self):
"""Test all supported compress algorithms"""
algorithms = ["lzo", "lz4", "lz4-v2", "stub", "stub-v2"]

for algo in algorithms:
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"compress": algo,
"dev": "tun0",
"dev_type": "tun",
"key": "key.pem",
"mode": "p2p",
"name": f"test-{algo}",
"proto": "udp",
"remote": [{"host": "vpn.example.com", "port": 1194}],
"tls_client": True,
}
]
}
)
output = c.render()
self.assertIn(f"compress {algo}", output)

def test_compress_stub(self):
"""Test compress with stub for compression framing without compression"""
c = OpenVpn(
{
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"compress": "stub",
"dev": "tun0",
"dev_type": "tun",
"key": "key.pem",
"mode": "p2p",
"name": "test-stub-compress",
"nobind": True,
"proto": "udp",
"remote": [{"host": "vpn.example.com", "port": 1194}],
"tls_client": True,
}
]
}
)
expected = """# openvpn config: test-stub-compress

ca ca.pem
cert cert.pem
compress stub
dev tun0
dev-type tun
key key.pem
mode p2p
nobind
proto udp
remote vpn.example.com 1194
tls-client
"""
self.assertEqual(c.render(), expected)
52 changes: 52 additions & 0 deletions tests/openvpn/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,55 @@ def test_file_path_min_length(self):
with self.assertRaises(ValidationError) as err:
OpenVpn(conf).generate()
self.assertEqual("'.' is too short", err.exception.message)

def test_parse_compress(self):
"""Test parsing compress option"""
native = """# openvpn config: test-server

ca ca.pem
cert cert.pem
compress lz4-v2
dev tap0
dev-type tap
dh dh.pem
key key.pem
mode server
proto udp
tls-server
"""
expected = {
"openvpn": [
{
"ca": "ca.pem",
"cert": "cert.pem",
"compress": "lz4-v2",
"dev": "tap0",
"dev_type": "tap",
"dh": "dh.pem",
"key": "key.pem",
"mode": "server",
"name": "test-server",
"proto": "udp",
"tls_server": True,
}
]
}
o = OpenVpn(native=native)
self.assertDictEqual(o.config, expected)

def test_parse_allow_compression(self):
"""Test parsing allow-compression option"""
native = """# openvpn config: test-server

allow-compression no
ca ca.pem
cert cert.pem
dev tap0
dev-type tap
dh dh.pem
key key.pem
mode server
proto udp
"""
o = OpenVpn(native=native)
self.assertEqual(o.config["openvpn"][0]["allow_compression"], "no")