Skip to content

Commit 571af14

Browse files
[feature:gsoc23] Added ZeroTier VPN backend & OpenWrt support #208 #284
Closes #208 Closes #284 Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent 68681a1 commit 571af14

File tree

23 files changed

+2160
-2
lines changed

23 files changed

+2160
-2
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Its main features are listed below for your reference:
4444
* `OpenWisp Firmware <https://github.com/openwisp/OpenWISP-Firmware>`_ support
4545
* `OpenVPN <https://openvpn.net>`_ support
4646
* `WireGuard <https://www.wireguard.com/>`_ support
47+
* `ZeroTier <https://www.zerotier.com/>`_ support
4748
* Possibility to support more firmwares via custom backends
4849
* Based on the `NetJSON RFC <http://netjson.org/rfc.html>`_
4950
* **Validation** based on `JSON-Schema <http://json-schema.org/>`_

docs/source/backends/vpn.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ VPN Backends
1010
/backends/openvpn
1111
/backends/wireguard
1212
/backends/vxlan_over_wireguard
13+
/backends/zerotier

docs/source/backends/zerotier.rst

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

docs/source/general/basics.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ The current implemented backends are:
103103
* :doc:`OpenVpn </backends/openvpn>` (custom backend implementing only OpenVPN configuration)
104104
* :doc:`WireGuard </backends/wireguard>` (custom backend implementing only WireGuard configuration)
105105
* :doc:`VXLAN over WireGuard </backends/vxlan_over_wireguard>` (custom backend implementing only VXLAN over WireGuard configuration)
106+
* :doc:`ZeroTier </backends/zerotier>` (custom backend implementing only ZeroTier configuration)
106107

107108
Example initialization of ``OpenWrt`` backend:
108109

netjsonconfig/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .backends.openwrt.openwrt import OpenWrt # noqa
88
from .backends.vxlan.vxlan_wireguard import VxlanWireguard # noqa
99
from .backends.wireguard.wireguard import Wireguard # noqa
10+
from .backends.zerotier.zerotier import ZeroTier # noqa
1011
from .version import VERSION, __version__, get_version # noqa
1112

1213

@@ -16,6 +17,7 @@ def get_backends():
1617
'openwisp': OpenWisp,
1718
'openvpn': OpenVpn,
1819
'wireguard': Wireguard,
20+
'zerotier': ZeroTier,
1921
}
2022
logger = logging.getLogger(__name__)
2123

netjsonconfig/backends/base/backend.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,9 @@ def _generate_contents(self, tar):
380380
# create a file for each VPN
381381
for vpn in vpn_instances:
382382
lines = vpn.split('\n')
383-
vpn_name = lines[0]
383+
# It's better to split lines[0] using
384+
# `config_suffix` to extract the correct vpn_name
385+
vpn_name = lines[0].split(self.config_suffix)[0]
384386
text_contents = '\n'.join(lines[2:])
385387
# do not end with double new line
386388
if text_contents.endswith('\n\n'):

netjsonconfig/backends/openwrt/converters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .switch import Switch
1111
from .wireguard_peers import WireguardPeers
1212
from .wireless import Wireless
13+
from .zerotier import ZeroTier
1314

1415
__all__ = [
1516
'Default',
@@ -24,4 +25,5 @@
2425
'Switch',
2526
'WireguardPeers',
2627
'Wireless',
28+
'ZeroTier',
2729
]

netjsonconfig/backends/openwrt/converters/openvpn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __intermediate_vpn(self, vpn):
1818
def __netjson_vpn(self, vpn):
1919
if vpn.get('server_bridge') == '1':
2020
vpn['server_bridge'] = ''
21-
# 'enabled' defaults to False in OpenWRT
21+
# 'disabled' defaults to False in OpenWRT
2222
vpn['disabled'] = vpn.pop('enabled', '0') == '0'
2323
vpn['name'] = vpn.pop('.name')
2424
del vpn['.type']
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from ...zerotier.converters import ZeroTier as BaseZeroTier
2+
from ..schema import schema
3+
from .base import OpenWrtConverter
4+
5+
6+
class ZeroTier(OpenWrtConverter, BaseZeroTier):
7+
_uci_types = ['zerotier']
8+
_schema = schema['properties']['zerotier']['items']
9+
10+
def __intermediate_vpn(self, vpn):
11+
nwid_ifnames = vpn.get('networks', [])
12+
files = self.netjson.get('files', [])
13+
self.netjson['files'] = self.__get_zt_ifname_files(vpn, files)
14+
vpn.update(
15+
{
16+
'.name': self._get_uci_name(vpn.pop('name')),
17+
'.type': 'zerotier',
18+
'config_path': vpn.get('config_path', '/etc/openwisp/zerotier'),
19+
'copy_config_path': vpn.get('copy_config_path', '1'),
20+
'join': [networks.get('id', '') for networks in nwid_ifnames],
21+
'enabled': not vpn.pop('disabled', False),
22+
}
23+
)
24+
del vpn['networks']
25+
return super().__intermediate_vpn(vpn, remove=[''])
26+
27+
def __netjson_vpn(self, vpn):
28+
nwids = vpn.pop('join')
29+
vpn['name'] = vpn.pop('.name')
30+
vpn['networks'] = [{"id": nwid, "ifname": f"owzt{nwid[-6:]}"} for nwid in nwids]
31+
# 'disabled' defaults to False in OpenWRT
32+
vpn['disabled'] = vpn.pop('enabled', '0') == '0'
33+
del vpn['.type']
34+
return super().__netjson_vpn(vpn)
35+
36+
def __get_zt_ifname_files(self, vpn, files):
37+
config_path = vpn.get('config_path', '/etc/openwisp/zerotier')
38+
nwid_ifnames = vpn.get('networks', [])
39+
zt_file_contents = '# network_id=interface_name\n'
40+
41+
for networks in nwid_ifnames:
42+
nwid = networks.get('id', '')
43+
ifname = networks.get('ifname')
44+
zt_file_contents += f"{nwid}={ifname}\n"
45+
46+
zt_interface_map = {
47+
'path': f"{config_path}/devicemap",
48+
'mode': '0644',
49+
'contents': zt_file_contents,
50+
}
51+
52+
if not files:
53+
return [zt_interface_map]
54+
updated_files = []
55+
for file in files:
56+
if file.get('path') == zt_interface_map.get('path'):
57+
zt_interface_map['contents'] += '\n' + file['contents']
58+
else:
59+
updated_files.append(file)
60+
if zt_interface_map.get('contents'):
61+
updated_files.append(zt_interface_map)
62+
return updated_files

netjsonconfig/backends/openwrt/openwrt.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ..base.backend import BaseBackend
44
from ..vxlan.vxlan_wireguard import VxlanWireguard
55
from ..wireguard.wireguard import Wireguard
6+
from ..zerotier.zerotier import ZeroTier
67
from . import converters
78
from .parser import OpenWrtParser, config_path, packages_pattern
89
from .renderer import OpenWrtRenderer
@@ -27,6 +28,7 @@ class OpenWrt(BaseBackend):
2728
converters.Wireless,
2829
converters.OpenVpn,
2930
converters.WireguardPeers,
31+
converters.ZeroTier,
3032
converters.Default,
3133
]
3234
parser = OpenWrtParser
@@ -142,6 +144,11 @@ def vxlan_wireguard_auto_client(cls, **kwargs):
142144
config['interfaces'].append(vxlan_interface)
143145
return config
144146

147+
@classmethod
148+
def zerotier_auto_client(cls, **kwargs):
149+
data = ZeroTier.auto_client(**kwargs)
150+
return {'zerotier': [data]}
151+
145152
def validate(self):
146153
self._validate_radios()
147154
super().validate()

0 commit comments

Comments
 (0)