diff --git a/data/configd-include.json b/data/configd-include.json
index 5a4912e3045..533eaa5ac31 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -28,6 +28,7 @@
"interfaces-openvpn.py",
"interfaces-pppoe.py",
"interfaces-pseudo-ethernet.py",
+"interfaces-tinc.py",
"interfaces-tunnel.py",
"interfaces-vti.py",
"interfaces-vxlan.py",
diff --git a/data/templates/tinc/hosts_config.tmpl b/data/templates/tinc/hosts_config.tmpl
new file mode 100644
index 00000000000..d05be658f67
--- /dev/null
+++ b/data/templates/tinc/hosts_config.tmpl
@@ -0,0 +1,7 @@
+{% for prefix in subnets %}
+Subnet = {{ prefix }}
+{% endfor %}
+{% for addr in local_address %}
+Address = {{ addr }}
+{% endfor %}
+Port = {{ port }}
diff --git a/data/templates/tinc/tinc.conf.tmpl b/data/templates/tinc/tinc.conf.tmpl
new file mode 100644
index 00000000000..65f80e2fbd4
--- /dev/null
+++ b/data/templates/tinc/tinc.conf.tmpl
@@ -0,0 +1,111 @@
+Name = {{ node_name }}
+Interface = {{ ifname }}
+{% if bridge %}
+Mode = {{ bridge }}
+{% else %}
+Mode = router
+{% endif %}
+Compression = {{ compression_level }}
+Cipher = {{ encryption.cipher }}
+Digest = {{ encryption.digset }}
+{% if resolve_hostname %}
+Hostnames = yes
+{% else %}
+Hostnames = no
+{% endif %}
+PrivateKeyFile = {{ private_keyfile }}
+Broadcast = {{ broadcast_type }}
+{% if disable_resolve_hostname %}
+DecrementTTL = no
+{% else %}
+DecrementTTL = yes
+{% endif %}
+{% if direct_only %}
+DirectOnly = yes
+{% else %}
+DirectOnly = no
+{% endif %}
+Forwarding = {{ forwarding_option }}
+{% if iff_One_Queue %}
+IffOneQueue = yes
+{% else %}
+IffOneQueue = no
+{% endif %}
+KeyExpire = {{ key_expire }}
+{% if local_discovery %}
+LocalDiscovery = yes
+{% else %}
+LocalDiscovery = no
+{% endif %}
+MACExpire = {{ mac_expire}}
+MaxTimeout = {{ max_timeout }}
+PingInterval = {{ ping_interval }}
+PingTimeout = {{ ping_timeout }}
+{% if priority_inheritance %}
+PriorityInheritance = yes
+{% else %}
+PriorityInheritance = no
+{% endif %}
+ProcessPriority = {{ priority }}
+ReplayWindow = {{ replay_window }}
+{% if strict_subnets %}
+StrictSubnets = yes
+{% else %}
+StrictSubnets = no
+{% endif %}
+{% if tunnel_server %}
+TunnelServer = yes
+{% else %}
+TunnelServer = no
+{% endif %}
+{% if clamp_mss %}
+ClampMSS = yes
+{% else %}
+ClampMSS = no
+{% endif %}
+{% if indirect_data %}
+IndirectData = yes
+{% else %}
+IndirectData = no
+{% endif %}
+MACLength = {{ mac_length }}
+PMTU = {{ mtu }}
+{% if disable_PMTU_Discovery %}
+PMTUDiscovery = no
+{% else %}
+PMTUDiscovery = yes
+{% endif %}
+{% if TCP_Only %}
+TCPonly = yes
+{% else %}
+TCPonly = no
+{% endif %}
+{% if udp_rcv_buf %}
+UDPRcvBuf = {{ udp_rcv_buf }}
+{% endif %}
+{% if udp_snd_buf %}
+UDPSndBuf = {{ udp_snd_buf }}
+{% endif %}
+{% if proxy and proxy.type %}
+{% if proxy.type == 'socks5' %}
+Proxy = {{ proxy.type }} {{ proxy.address }} {{ proxy.port }} {{ proxy.username }} { proxy.password }}
+{% elif proxy.type == 'socks4' %}
+Proxy = {{ proxy.type }} {{ proxy.address }} {{ proxy.port }}{{ proxy.username }}
+{% elif proxy.type == 'http' %}
+Proxy = {{ proxy.type }} {{ proxy.address }} {{ proxy.port }}
+{% elif proxy.type == 'exec' %}
+Proxy = {{ proxy.type }} {{ proxy.exec }}
+{% endif %}
+{% endif %}
+{% for conn in connect %}
+ConnectTo = {{ conn }}
+{% endfor %}
+{% if bind_address %}
+BindToAddress = {{ bind_address }}
+{% endif %}
+{% if bind_interface %}
+BindToInterface = {{ bind_interface }}
+{% endif %}
+{% if graph_dump_file %}
+GraphDumpFile = {{ graph_dump_file }}
+{% endif %}
diff --git a/debian/control b/debian/control
index 0db098be65a..e5417fde82b 100644
--- a/debian/control
+++ b/debian/control
@@ -175,7 +175,9 @@ Depends:
wireless-regdb,
wpasupplicant (>= 0.6.7),
ndppd,
- miniupnpd-nftables
+ miniupnpd-nftables,
+ wpasupplicant (>= 0.6.7),
+ tinc
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/interface-definitions/interfaces-tinc.xml.in b/interface-definitions/interfaces-tinc.xml.in
new file mode 100644
index 00000000000..90da27a6ed3
--- /dev/null
+++ b/interface-definitions/interfaces-tinc.xml.in
@@ -0,0 +1,451 @@
+
+
+
+
+
+
+ Tinc VPN Tunnel Interface
+ 460
+
+ ^tinc[0-9]+$
+
+ Tinc VPN tunnel interface must be named vtincN
+
+ tincN
+ Tinc VPN interface name
+
+
+
+
+
+ Local Node Name options(require)
+
+
+
+
+ Declare network segment
+
+ ipv4net
+ IPv4 address and prefix length
+
+
+ ipv6net
+ IPv6 address and prefix length
+
+
+
+
+
+
+
+
+
+
+ Bind To Address
+
+ ipv4net
+ IPv4 address and prefix length
+
+
+ ipv6net
+ IPv6 address and prefix length
+
+
+
+
+
+
+
+
+
+
+ IP address
+
+ ipv4
+ IPv4 address
+
+
+ ipv6
+ IPv6 address
+
+
+ host
+ Host
+
+
+
+
+
+
+
+
+
+ Port Option
+
+
+
+
+ 655
+
+
+
+ Bind To Interface
+
+
+
+
+
+
+
+ Connect To Peer Node Name
+
+
+
+
+ The host public key configuration file to be connected
+
+ user@ip:path
+ SCP Protocol Copy
+
+
+ path
+ Local Protocol Copy
+
+
+
+
+
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+
+
+ Data Encryption settings
+
+
+
+
+ Standard Data Encryption Algorithm(default:aes-256-cbc)
+
+ aes-256-cbc
+
+
+
+ UDP Digest settings(default:sha256)
+
+ sha256
+
+
+
+
+
+ Tinc VPN interface device Bridge Mode(If this option is not specified, tinc will run in routing mode)
+
+ switch hub
+
+
+ switch
+ switch device
+
+
+ hub
+ hub device
+
+
+ (switch|hub)
+
+
+
+ #include
+ #include
+ #include
+ #include
+
+
+ This option selects the way broadcast packets are sent to other daemons. NOTE: all nodes in a VPN must use the same Broadcast mode, otherwise routing loops can form(defult: direct)
+
+ no mst
+
+
+ no
+ Broadcast packets are never sent to other nodes
+
+
+ mst
+ Broadcast packets are sent and forwarded via the VPN’s Minimum Spanning Tree. This ensures broadcast packets reach all nodes
+
+
+ (no|mst)
+
+
+ direct
+
+
+
+ Disable Decrement TTL
+
+
+
+
+
+ Only carry out peer-to-peer direct communication, discard data packets that need to be forwarded
+
+
+
+
+
+ This option selects the way indirect packets are forwarded(default: internal)
+
+ off kernel
+
+
+ off
+ Incoming packets that are not meant for the local node, but which should be forwarded to another node, are dropped
+
+
+ kernel
+ Incoming packets are always sent to the TUN/TAP device, even if the packets are not for the local node. This is less efficient, but allows the kernel to apply its routing and firewall rules on them, and can also help debugging
+
+
+ (off|kernel)
+
+
+ internal
+
+
+
+ Dump network graph files, which can be read with graphviz
+
+
+
+
+ Resolve IP addresses (real and VPN)
+
+
+
+
+
+ Set IFF_ONE_QUEUE flag on TUN/TAP devices
+
+
+
+
+
+ Key expiration time(seconds,default:3600)
+
+ 3600
+
+
+
+ Enable Local Discovery
+
+
+
+
+
+ MAC expiration time(seconds,default:600)
+
+ 600
+
+
+
+ Max Timeout(seconds,default:900)
+
+ 900
+
+
+
+ Ping Interval(seconds,default:60)
+
+ 60
+
+
+
+ Ping Timeout(seconds,default:5)
+
+ 5
+
+
+
+ Enable Priority Inheritance
+
+
+
+
+
+ Private Key File,This is the full path name of the RSA private key file that was generated by ‘tincd --generate-keys’. It must be a full path, not a relative directory(require)
+
+
+
+
+ The directory where the network interface stores host certificates(require)
+
+
+
+
+ Priority Option
+
+ low normal high
+
+
+ low
+ Low Priority
+
+
+ normal
+ normal Priority(default)
+
+
+ high
+ high Priority
+
+
+ (low|normal|high)
+
+
+ normal
+
+
+
+ Proxy settings
+
+
+
+
+ Proxy Type
+
+ socks4 socks5 http exec
+
+
+ socks4
+ socks4 Proxy
+
+
+ socks5
+ socks4 Proxy
+
+
+ http
+ http Proxy
+
+
+ exec
+ Executes the given command which should set up the outgoing connection. The environment variables NAME, NODE, REMOTEADDRES and REMOTEPORT are available
+
+
+ (socks4|socks5|http|exec)
+
+
+
+
+
+ Proxy Address
+
+
+
+
+
+
+
+ Proxy Port
+
+
+
+
+
+
+
+ Proxy Username
+
+
+
+
+ Proxy Password
+
+
+
+
+ Executes the given command which should set up the outgoing connection. The environment variables NAME, NODE, REMOTEADDRES and REMOTEPORT are available
+
+
+
+
+
+
+ ReplayWindow(Bytes,default:16)
+
+ 16
+
+
+
+ When this option is enabled tinc will only use Subnet statements which are present in the host config files in the local /etc/tinc/netname/hosts/ directory. Subnets learned via connections to other nodes and which are not present in the local host config files are ignored
+
+
+
+
+
+ When this option is enabled tinc will no longer forward information between other tinc daemons, and will only allow connections with nodes for which host config files are present in the local /etc/tinc/netname/hosts/ directory, Setting this options also implicitly sets StrictSubnets
+
+
+
+
+
+ Sets the socket receive buffer size for the UDP socket, in bytes. If unset, the default buffer size will be used by the operating system(Bytes)
+
+
+
+
+ Sets the socket send buffer size for the UDP socket, in bytes. If unset, the default buffer size will be used by the operating system(Bytes)
+
+
+
+
+ Enable Clamp MSS
+
+
+
+
+
+ The tinc daemons other than those specified by ConnectTo can directly establish a connection with you
+
+
+
+
+
+ Disable PMTU Discovery Option
+
+
+
+
+
+ Only use TCP connection
+
+
+
+
+
+ Compression Level Option(default:9)
+
+ 9
+
+
+
+ MAC Length(Bytes,default:4)
+
+ 4
+
+
+
+ MTU(Bytes,default:1514)
+
+ 1514
+
+
+
+
+
+
diff --git a/op-mode-definitions/tinc.xml.in b/op-mode-definitions/tinc.xml.in
new file mode 100644
index 00000000000..c29ba32ebcf
--- /dev/null
+++ b/op-mode-definitions/tinc.xml.in
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+ Show Tinc VPN interface information
+
+
+
+
+ Show detailed Tinc VPN interface information
+
+ ${vyos_op_scripts_dir}/show_interfaces.py --intf-type=tinc --action=show
+
+
+
+
+
+ Show Tinc VPN interface information
+
+
+
+
+ ${vyos_op_scripts_dir}/show_interfaces.py --intf=$4
+
+
+
+ Show summary of specified Tinc VPN interface information
+
+ ${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief
+
+
+
+
+
+
+
+
+
+
+
+ Generate tinc vpn tunnel configuration
+
+
+
+
+
+
+
+ Generate tinc vpn tunnel host configuration path
+
+ scp "$5" "/etc/tinc/$3/hosts/"
+
+
+
+
+
+
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index a37615c8f8c..a8b9a2691ce 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -30,6 +30,7 @@
from vyos.ifconfig.vxlan import VXLANIf
from vyos.ifconfig.wireguard import WireGuardIf
from vyos.ifconfig.vtun import VTunIf
+from vyos.ifconfig.tinc import TincIf
from vyos.ifconfig.vti import VTIIf
from vyos.ifconfig.pppoe import PPPoEIf
from vyos.ifconfig.tunnel import TunnelIf
diff --git a/python/vyos/ifconfig/tinc.py b/python/vyos/ifconfig/tinc.py
new file mode 100644
index 00000000000..8261693fd24
--- /dev/null
+++ b/python/vyos/ifconfig/tinc.py
@@ -0,0 +1,60 @@
+# Copyright 2020 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see .
+
+from vyos.ifconfig.interface import Interface
+from vyos.validate import assert_ip
+
+
+@Interface.register
+class TincIf(Interface):
+ default = {
+ 'type': 'tinc',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'tinc',
+ 'prefixes': ['tinc'],
+ 'bridgeable': True,
+ 'eternal': '(tinc)[0-9]+$',
+ 'bondable': True,
+ 'broadcast': True
+ }
+ }
+ _command_set = {
+ **Interface._command_set,
+ 'addr' : {
+ 'validate': assert_ip,
+ 'shellcmd': 'ip link set dev {ifname} local {value}',
+ }
+ }
+
+ def set_addr(self, addr):
+ return self.set_interface('addr', addr)
+
+ def update(self, config):
+ # Enable/Disable of an interface must always be done at the end of the
+ # derived class to make use of the ref-counting set_admin_state()
+ # function. We will only enable the interface if 'up' was called as
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ super().update(config)
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)
+
+ for addr in tinc['address']:
+ self.set_addr(address)
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index a831933632c..eee221f7598 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -195,6 +195,9 @@ def assert_positive(n, smaller=0):
if int(n) < smaller:
raise ValueError(f'{n} is smaller than {smaller}')
+def assert_ip(addr):
+ if is_ip(addr):
+ raise ValueError(f'Address {addr} is invalid')
def assert_mtu(mtu, ifname):
assert_number(mtu)
diff --git a/smoketest/scripts/cli/test_interfaces_tinc.py b/smoketest/scripts/cli/test_interfaces_tinc.py
new file mode 100755
index 00000000000..0fc1aabb056
--- /dev/null
+++ b/smoketest/scripts/cli/test_interfaces_tinc.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import unittest
+
+from base_interfaces_test import BasicInterfaceTest
+from vyos.ifconfig import Section
+
+base_path = ['interfaces','tinc']
+
+class TaskTincVPN(unittest.TestCase):
+ def setUp(self):
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ self.session = ConfigSession(os.getpid())
+ self.session.delete(base_path)
+
+ def tearDown(self):
+ self.session.delete(base_path)
+ self.session.commit()
+ def test_ndp_proxy(self):
+ self.session.set(base_path + ['node-name'],'test1')
+ self.session.set(base_path + ['hosts-dir'],'/tmp/test/hosts')
+ self.session.set(base_path + ['private-keyfile'],'/tmp/test/test.key')
+ self.session.set(base_path + ['address'],'192.168.20.1/24')
+ self.session.set(base_path + ['subnets'],'192.168.20.0/24')
+ # check validate() - outbound-interface must be defined
+ with self.assertRaises(ConfigSessionError):
+ self.session.commit()
+ self.assertEqual(True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/conf_mode/interfaces-tinc.py b/src/conf_mode/interfaces-tinc.py
new file mode 100644
index 00000000000..aae9496cd38
--- /dev/null
+++ b/src/conf_mode/interfaces-tinc.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import shutil
+
+from sys import exit
+
+from netifaces import interfaces
+from vyos.configdict import get_interface_dict,leaf_node_changed
+from vyos.config import Config
+from time import sleep
+from vyos.ifconfig import TincIf
+from vyos import ConfigError
+from vyos.util import call
+from vyos.template import render
+from vyos.configverify import verify_address
+from vyos.configverify import verify_dhcpv6
+from vyos.configverify import verify_vrf
+
+from vyos import airbag
+
+airbag.enable()
+
+def get_config(config = None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['interfaces', 'tinc']
+
+ _, tinc = get_interface_dict(conf, base)
+
+ if 'network_dir' in tinc:
+ tinc_network_dir = tinc['network_dir']
+ ed25516_private_key = f'{tinc_network_dir}/ed25519_key.priv'
+ rsa_private_key = f'{tinc_network_dir}/rsa_key.priv'
+ tinc.update({'ed25516_private_key': ed25516_private_key, 'rsa_private_key': rsa_private_key});
+
+ if 'deleted' in tinc:
+ tmp = leaf_node_changed(conf, ['network-dir'])
+ if tmp and len(tmp) == 1:
+ tinc.update({'network_dir':tmp[0]})
+
+ return tinc
+
+def verify(tinc):
+ #bail out early - looks like removal from running config
+ if tinc is None:
+ return None
+ network = tinc['ifname']
+ if 'deleted' in tinc or 'disable' in tinc:
+ return None
+ if 'address' not in tinc:
+ raise ConfigError('address must be set')
+ if 'network_dir' not in tinc:
+ tinc.update({'network_dir': f'/config/tinc/{network}'})
+ if 'subnets' not in tinc:
+ raise ConfigError('subnets must be set')
+ if 'node_name' not in tinc:
+ raise ConfigError('node_name must be set')
+ if 'connect' in tinc:
+ node_name = tinc['node_name']
+ connect_peer_node_name = tinc['connect']
+ if node_name == connect_peer_node_name:
+ raise ConfigError('The local node name("{local_node}") and the remote target connection node name("{remote_node}") cannot match'.format(local_node = node_name, remote_node = connect_peer_node_name))
+
+ if 'proxy' in tinc:
+ if 'address' not in tinc['proxy']:
+ raise ConfigError('proxy.address must be set')
+ if 'port' not in tinc['proxy']:
+ raise ConfigError('proxy.port must be set')
+ if 'type' not in tinc['proxy']:
+ raise ConfigError('proxy.type must be set')
+ if tinc['proxy']['type'] == 'socks5':
+ if 'password' not in tinc['proxy']:
+ raise ConfigError('proxy.password must be set')
+ if tinc['proxy']['type'] == 'socks4' or tinc['proxy']['type'] == 'socks5':
+ if 'username' not in tinc['proxy']:
+ raise ConfigError('proxy.username must be set')
+ if tinc['proxy']['type'] == 'exec':
+ if 'exec' not in tinc:
+ raise ConfigError('proxy.exec must be set')
+
+ if 'connect' in tinc:
+ for conn in tinc['connect']:
+ if 'conf_path' in tinc['connect'][conn] and not os.path.exists(tinc['connect'][conn]['conf_path']):
+ raise ConfigError('The configuration file connected to peer {peer} is missing'.format(peer = conn))
+
+ verify_dhcpv6(tinc)
+ verify_address(tinc)
+ verify_vrf(tinc)
+
+ if {'is_bond_member', 'mac'} <= set(tinc):
+ print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" '
+ f'is a member of bond "{is_bond_member}"'.format(**tinc))
+
+ return None
+
+def create_or_delete_network(tinc, op):
+ if tinc is None:
+ return None
+ interface = tinc['ifname']
+ network = tinc['ifname']
+ system_network_dir = f'/etc/tinc/{network}'
+ tinc_network_dir = tinc['network_dir']
+
+ if tinc is None:
+ return None
+
+ if op == "delete":
+ if os.path.exists(system_network_dir):
+ shutil.rmtree(system_network_dir)
+ if os.path.exists(tinc_network_dir):
+ shutil.rmtree(tinc_network_dir)
+ elif op == 'create':
+ if not os.path.exists(tinc_network_dir):
+ os.makedirs(tinc_network_dir, mode = 0o666 )
+ if not os.path.exists(f'{tinc_network_dir}/hosts'):
+ os.makedirs(f'{tinc_network_dir}/hosts', mode = 0o666 )
+ if not os.path.exists(system_network_dir):
+ os.makedirs(system_network_dir, mode = 0o666 )
+ call(f'chown vyos -R {tinc_network_dir}')
+ call(f'chown vyos -R {system_network_dir}')
+ call(f'chmod a+x -R {tinc_network_dir}')
+ call(f'chmod a+x -R {system_network_dir}')
+ if not os.path.exists(f'{system_network_dir}/hosts'):
+ os.symlink(f'{tinc_network_dir}/hosts', f'{system_network_dir}/hosts')
+ return None
+
+def generate(tinc):
+ if tinc is None:
+ return None
+ if 'deleted' in tinc or 'disable' in tinc:
+ return None
+ interface = tinc['ifname']
+ network = tinc['ifname']
+ node_name = tinc['node_name']
+ keys_length = tinc['keys_length']
+ tinc_network_dir = tinc['network_dir']
+ rsa_private_keyfile = f'{tinc_network_dir}/rsa_key.priv'
+ ed25519_private_keyfile = f'{tinc_network_dir}/ed25519_key.priv'
+ tinc_hosts_dir = f'/{tinc_network_dir}/hosts'
+ system_network_dir = f'/etc/tinc/{network}'
+ tinc_main_config = f'{system_network_dir}/tinc.conf'
+ tinc_host_local_peer_config = f'{tinc_network_dir}/hosts/{node_name}'
+ if tinc:
+ if not os.path.exists(system_network_dir) or not os.path.exists(tinc_network_dir):
+ create_or_delete_network(tinc, 'create') # Create a directory
+ call(f'tinc -b -n {network} init {node_name}')
+ if not os.path.exists(rsa_private_keyfile) and not os.path.exists(ed25519_private_keyfile) and not os.path.exists(tinc_host_local_peer_config) :
+ render(tinc_host_local_peer_config, 'tinc/hosts_config.tmpl', tinc)
+ call(f'tinc -b -n {network} generate-keys {keys_length}')
+ shutil.move(f'{system_network_dir}/ed25519_key.priv', f'{tinc_network_dir}/ed25519_key.priv')
+ shutil.move(f'{system_network_dir}/rsa_key.priv', f'{tinc_network_dir}/rsa_key.priv')
+ elif not os.path.exists(rsa_private_keyfile) or not os.path.exists(ed25519_private_keyfile) or not os.path.exists(tinc_host_local_peer_config):
+ if os.path.exists(rsa_private_keyfile):
+ os.remove(rsa_private_keyfile)
+ if os.path.exists(ed25519_private_keyfile):
+ os.remove(ed25519_private_keyfile)
+ if os.path.exists(tinc_host_local_peer_config):
+ os.remove(tinc_host_local_peer_config)
+ render(tinc_host_local_peer_config, 'tinc/hosts_config.tmpl', tinc)
+ call(f'tinc -b -n {network} generate-keys {keys_length}')
+ shutil.move(f'{system_network_dir}/ed25519_key.priv', f'{tinc_network_dir}/ed25519_key.priv')
+ shutil.move(f'{system_network_dir}/rsa_key.priv', f'{tinc_network_dir}/rsa_key.priv')
+ else:
+ try:
+ call(f'tinc -n {network} del subnet')
+ call(f'tinc -n {network} del address')
+ call(f'tinc -n {network} del port')
+ if 'subnets' in tinc:
+ for subnet in tinc['subnets']:
+ call(f'tinc -n {network} add subnet {subnet}')
+ if 'local_address' in tinc:
+ for address in tinc['local_address']:
+ call(f'tinc -n {network} add address {address}')
+ port = tinc['port']
+ call(f'tinc -n {network} add port {port}')
+ except:
+ pass
+ render(tinc_main_config, 'tinc/tinc.conf.tmpl', tinc)
+ if 'connect' in tinc:
+ for conn in tinc['connect']:
+ if 'conf_path' in tinc['connect'][conn]:
+ conf = f'{tinc_hosts_dir}/{conn}'
+ system_conf = f'{system_network_dir}/hosts/{conn}'
+ if tinc['connect'][conn]['conf_path'] != conf and tinc['connect'][conn]['conf_path'] != system_conf:
+ base = tinc['connect'][conn]['conf_path']
+ tmp = f'/tmp/remote_tinc_{conn}'
+ os.symlink(base, conf)
+
+ return None
+
+def apply(tinc):
+ if tinc is None:
+ return None
+ interface = tinc['ifname']
+ if 'deleted' in tinc or 'disable' in tinc:
+ network = tinc['ifname']
+ call(f'systemctl stop tinc@{network}')
+ if 'deleted' in tinc:
+ create_or_delete_network(tinc, 'delete') # Delete a directory
+ try:
+ t = TincIf(interface)
+ # Delete Interface
+ t.remove()
+ except:
+ pass
+ else:
+ network = tinc['ifname']
+ call(f'systemctl restart tinc@{network}')
+
+ # sleep 250ms
+ sleep(0.250)
+ try:
+ t = TincIf(interface)
+ #update interface description used e.g. within SNMP
+ t.update(tinc)
+ except:
+ pass
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)