summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/accel_l2tp.py397
-rwxr-xr-xsrc/conf_mode/arp.py6
-rwxr-xr-xsrc/conf_mode/bcast_relay.py19
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py32
-rwxr-xr-xsrc/conf_mode/dhcp_server.py56
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py29
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py54
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py23
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py46
-rwxr-xr-xsrc/conf_mode/firewall_options.py26
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py31
-rwxr-xr-xsrc/conf_mode/host_name.py16
-rwxr-xr-xsrc/conf_mode/http-api.py7
-rwxr-xr-xsrc/conf_mode/https.py19
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py20
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py27
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py22
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py22
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py5
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py352
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py58
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py52
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py139
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py26
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py412
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py156
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py54
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py39
-rwxr-xr-xsrc/conf_mode/le_cert.py16
-rwxr-xr-xsrc/conf_mode/lldp.py25
-rwxr-xr-xsrc/conf_mode/mdns_repeater.py21
-rwxr-xr-xsrc/conf_mode/ntp.py21
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py18
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py18
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py21
-rwxr-xr-xsrc/conf_mode/protocols_pim.py18
-rwxr-xr-xsrc/conf_mode/salt-minion.py19
-rwxr-xr-xsrc/conf_mode/service-ipoe.py20
-rwxr-xr-xsrc/conf_mode/service-pppoe.py19
-rwxr-xr-xsrc/conf_mode/service-router-advert.py19
-rwxr-xr-xsrc/conf_mode/snmp.py49
-rwxr-xr-xsrc/conf_mode/ssh.py19
-rwxr-xr-xsrc/conf_mode/system-ip.py4
-rwxr-xr-xsrc/conf_mode/system-ipv6.py4
-rwxr-xr-xsrc/conf_mode/system-login.py51
-rwxr-xr-xsrc/conf_mode/system-options.py4
-rwxr-xr-xsrc/conf_mode/system-syslog.py25
-rwxr-xr-xsrc/conf_mode/system-timezone.py4
-rwxr-xr-xsrc/conf_mode/system-wifi-regdom.py21
-rwxr-xr-xsrc/conf_mode/tftp_server.py19
-rwxr-xr-xsrc/conf_mode/vpn-pptp.py21
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py386
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py130
-rwxr-xr-xsrc/conf_mode/vrf.py16
-rwxr-xr-xsrc/conf_mode/vrrp.py30
55 files changed, 1381 insertions, 1782 deletions
diff --git a/src/conf_mode/accel_l2tp.py b/src/conf_mode/accel_l2tp.py
deleted file mode 100755
index 4ca5a858a..000000000
--- a/src/conf_mode/accel_l2tp.py
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-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 <http://www.gnu.org/licenses/>.
-
-import sys
-import os
-import re
-import jinja2
-import socket
-import time
-
-from jinja2 import FileSystemLoader, Environment
-
-from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
-from vyos import ConfigError
-from vyos.util import run
-
-
-pidfile = r'/var/run/accel_l2tp.pid'
-l2tp_cnf_dir = r'/etc/accel-ppp/l2tp'
-chap_secrets = l2tp_cnf_dir + '/chap-secrets'
-l2tp_conf = l2tp_cnf_dir + '/l2tp.config'
-# accel-pppd -d -c /etc/accel-ppp/l2tp/l2tp.config -p /var/run/accel_l2tp.pid
-
-# config path creation
-if not os.path.exists(l2tp_cnf_dir):
- os.makedirs(l2tp_cnf_dir)
-
-###
-# inline helper functions
-###
-# depending on hw and threads, daemon needs a little to start
-# if it takes longer than 100 * 0.5 secs, exception is being raised
-# not sure if that's the best way to check it, but it worked so far quite well
-###
-
-
-def chk_con():
- cnt = 0
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- while True:
- try:
- s.connect(("127.0.0.1", 2004))
- break
- except ConnectionRefusedError:
- time.sleep(0.5)
- cnt += 1
- if cnt == 100:
- raise("failed to start l2tp server")
- break
-
-
-def _accel_cmd(command):
- return run(f'/usr/bin/accel-cmd -p 2004 {command}')
-
-###
-# inline helper functions end
-###
-
-
-def get_config():
- c = Config()
- if not c.exists('vpn l2tp remote-access '):
- return None
-
- c.set_level('vpn l2tp remote-access')
- config_data = {
- 'authentication': {
- 'mode': 'local',
- 'local-users': {
- },
- 'radiussrv': {},
- 'radiusopt': {},
- 'auth_proto': [],
- 'mppe': 'prefer'
- },
- 'outside_addr': '',
- 'gateway_address': '10.255.255.0',
- 'dns': [],
- 'dnsv6': [],
- 'wins': [],
- 'client_ip_pool': None,
- 'client_ip_subnets': [],
- 'client_ipv6_pool': {},
- 'mtu': '1436',
- 'ip6_column': '',
- 'ip6_dp_column': '',
- 'ppp_options': {},
- }
-
- ### general options ###
-
- if c.exists('dns-servers server-1'):
- config_data['dns'].append(c.return_value('dns-servers server-1'))
- if c.exists('dns-servers server-2'):
- config_data['dns'].append(c.return_value('dns-servers server-2'))
- if c.exists('dnsv6-servers'):
- for dns6_server in c.return_values('dnsv6-servers'):
- config_data['dnsv6'].append(dns6_server)
- if c.exists('wins-servers server-1'):
- config_data['wins'].append(c.return_value('wins-servers server-1'))
- if c.exists('wins-servers server-2'):
- config_data['wins'].append(c.return_value('wins-servers server-2'))
- if c.exists('outside-address'):
- config_data['outside_addr'] = c.return_value('outside-address')
-
- # auth local
- if c.exists('authentication mode local'):
- if c.exists('authentication local-users username'):
- for usr in c.list_nodes('authentication local-users username'):
- config_data['authentication']['local-users'].update(
- {
- usr: {
- 'passwd': '',
- 'state': 'enabled',
- 'ip': '*',
- 'upload': None,
- 'download': None
- }
- }
- )
-
- if c.exists('authentication local-users username ' + usr + ' password'):
- config_data['authentication']['local-users'][usr]['passwd'] = c.return_value(
- 'authentication local-users username ' + usr + ' password')
- if c.exists('authentication local-users username ' + usr + ' disable'):
- config_data['authentication']['local-users'][usr]['state'] = 'disable'
- if c.exists('authentication local-users username ' + usr + ' static-ip'):
- config_data['authentication']['local-users'][usr]['ip'] = c.return_value(
- 'authentication local-users username ' + usr + ' static-ip')
- if c.exists('authentication local-users username ' + usr + ' rate-limit download'):
- config_data['authentication']['local-users'][usr]['download'] = c.return_value(
- 'authentication local-users username ' + usr + ' rate-limit download')
- if c.exists('authentication local-users username ' + usr + ' rate-limit upload'):
- config_data['authentication']['local-users'][usr]['upload'] = c.return_value(
- 'authentication local-users username ' + usr + ' rate-limit upload')
-
- # authentication mode radius servers and settings
-
- if c.exists('authentication mode radius'):
- config_data['authentication']['mode'] = 'radius'
- rsrvs = c.list_nodes('authentication radius server')
- for rsrv in rsrvs:
- if c.return_value('authentication radius server ' + rsrv + ' fail-time') == None:
- ftime = '0'
- else:
- ftime = str(c.return_value(
- 'authentication radius server ' + rsrv + ' fail-time'))
- if c.return_value('authentication radius-server ' + rsrv + ' req-limit') == None:
- reql = '0'
- else:
- reql = str(c.return_value(
- 'authentication radius server ' + rsrv + ' req-limit'))
-
- config_data['authentication']['radiussrv'].update(
- {
- rsrv: {
- 'secret': c.return_value('authentication radius server ' + rsrv + ' key'),
- 'fail-time': ftime,
- 'req-limit': reql
- }
- }
- )
- # Source ip address feature
- if c.exists('authentication radius source-address'):
- config_data['authentication']['radius_source_address'] = c.return_value(
- 'authentication radius source-address')
-
- # advanced radius-setting
- if c.exists('authentication radius acct-timeout'):
- config_data['authentication']['radiusopt']['acct-timeout'] = c.return_value(
- 'authentication radius acct-timeout')
- if c.exists('authentication radius max-try'):
- config_data['authentication']['radiusopt']['max-try'] = c.return_value(
- 'authentication radius max-try')
- if c.exists('authentication radius timeout'):
- config_data['authentication']['radiusopt']['timeout'] = c.return_value(
- 'authentication radius timeout')
- if c.exists('authentication radius nas-identifier'):
- config_data['authentication']['radiusopt']['nas-id'] = c.return_value(
- 'authentication radius nas-identifier')
- if c.exists('authentication radius dae-server'):
- # Set default dae-server port if not defined
- if c.exists('authentication radius dae-server port'):
- dae_server_port = c.return_value(
- 'authentication radius dae-server port')
- else:
- dae_server_port = "3799"
- config_data['authentication']['radiusopt'].update(
- {
- 'dae-srv': {
- 'ip-addr': c.return_value('authentication radius dae-server ip-address'),
- 'port': dae_server_port,
- 'secret': str(c.return_value('authentication radius dae-server secret'))
- }
- }
- )
- # filter-id is the internal accel default if attribute is empty
- # set here as default for visibility which may change in the future
- if c.exists('authentication radius rate-limit enable'):
- if not c.exists('authentication radius rate-limit attribute'):
- config_data['authentication']['radiusopt']['shaper'] = {
- 'attr': 'Filter-Id'
- }
- else:
- config_data['authentication']['radiusopt']['shaper'] = {
- 'attr': c.return_value('authentication radius rate-limit attribute')
- }
- if c.exists('authentication radius rate-limit vendor'):
- config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value(
- 'authentication radius rate-limit vendor')
-
- if c.exists('client-ip-pool'):
- if c.exists('client-ip-pool start') and c.exists('client-ip-pool stop'):
- config_data['client_ip_pool'] = c.return_value(
- 'client-ip-pool start') + '-' + re.search('[0-9]+$', c.return_value('client-ip-pool stop')).group(0)
-
- if c.exists('client-ip-pool subnet'):
- config_data['client_ip_subnets'] = c.return_values(
- 'client-ip-pool subnet')
-
- if c.exists('client-ipv6-pool prefix'):
- config_data['client_ipv6_pool']['prefix'] = c.return_values(
- 'client-ipv6-pool prefix')
- config_data['ip6_column'] = 'ip6,'
- if c.exists('client-ipv6-pool delegate-prefix'):
- config_data['client_ipv6_pool']['delegate_prefix'] = c.return_values(
- 'client-ipv6-pool delegate-prefix')
- config_data['ip6_dp_column'] = 'ip6-dp,'
-
- if c.exists('mtu'):
- config_data['mtu'] = c.return_value('mtu')
-
- # gateway address
- if c.exists('gateway-address'):
- config_data['gateway_address'] = c.return_value('gateway-address')
- else:
- # calculate gw-ip-address
- if c.exists('client-ip-pool start'):
- # use start ip as gw-ip-address
- config_data['gateway_address'] = c.return_value(
- 'client-ip-pool start')
- elif c.exists('client-ip-pool subnet'):
- # use first ip address from first defined pool
- lst_ip = re.findall("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", c.return_values(
- 'client-ip-pool subnet')[0])
- config_data['gateway_address'] = lst_ip[0]
-
- if c.exists('authentication require'):
- auth_mods = {'pap': 'pap', 'chap': 'auth_chap_md5',
- 'mschap': 'auth_mschap_v1', 'mschap-v2': 'auth_mschap_v2'}
- for proto in c.return_values('authentication require'):
- config_data['authentication']['auth_proto'].append(
- auth_mods[proto])
- else:
- config_data['authentication']['auth_proto'] = ['auth_mschap_v2']
-
- if c.exists('authentication mppe'):
- config_data['authentication']['mppe'] = c.return_value(
- 'authentication mppe')
-
- if c.exists('idle'):
- config_data['idle_timeout'] = c.return_value('idle')
-
- # LNS secret
- if c.exists('lns shared-secret'):
- config_data['lns_shared_secret'] = c.return_value('lns shared-secret')
-
- if c.exists('ccp-disable'):
- config_data['ccp_disable'] = True
-
- # ppp_options
- ppp_options = {}
- if c.exists('ppp-options'):
- if c.exists('ppp-options lcp-echo-failure'):
- ppp_options['lcp-echo-failure'] = c.return_value(
- 'ppp-options lcp-echo-failure')
- if c.exists('ppp-options lcp-echo-interval'):
- ppp_options['lcp-echo-interval'] = c.return_value(
- 'ppp-options lcp-echo-interval')
-
- if len(ppp_options) != 0:
- config_data['ppp_options'] = ppp_options
-
- return config_data
-
-
-def verify(c):
- if c == None:
- return None
-
- if c['authentication']['mode'] == 'local':
- if not c['authentication']['local-users']:
- raise ConfigError(
- 'l2tp-server authentication local-users required')
- for usr in c['authentication']['local-users']:
- if not c['authentication']['local-users'][usr]['passwd']:
- raise ConfigError('user ' + usr + ' requires a password')
-
- if c['authentication']['mode'] == 'radius':
- if len(c['authentication']['radiussrv']) == 0:
- raise ConfigError('radius server required')
- for rsrv in c['authentication']['radiussrv']:
- if c['authentication']['radiussrv'][rsrv]['secret'] == None:
- raise ConfigError('radius server ' + rsrv +
- ' needs a secret configured')
-
- # check for the existence of a client ip pool
- if not c['client_ip_pool'] and not c['client_ip_subnets']:
- raise ConfigError(
- "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
-
- # check ipv6
- if 'delegate_prefix' in c['client_ipv6_pool'] and not 'prefix' in c['client_ipv6_pool']:
- raise ConfigError(
- "\"set vpn l2tp remote-access client-ipv6-pool prefix\" required for delegate-prefix ")
-
- if len(c['dnsv6']) > 3:
- raise ConfigError("Maximum allowed dnsv6-servers addresses is 3")
-
-
-def generate(c):
- if c == None:
- return None
-
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'l2tp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- # accel-cmd reload doesn't work so any change results in a restart of the daemon
- try:
- if os.cpu_count() == 1:
- c['thread_cnt'] = 1
- else:
- c['thread_cnt'] = int(os.cpu_count()/2)
- except KeyError:
- if os.cpu_count() == 1:
- c['thread_cnt'] = 1
- else:
- c['thread_cnt'] = int(os.cpu_count()/2)
-
- tmpl = env.get_template('l2tp.config.tmpl')
- config_text = tmpl.render(c)
- open(l2tp_conf, 'w').write(config_text)
-
- if c['authentication']['local-users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
- old_umask = os.umask(0o077)
- open(chap_secrets, 'w').write(chap_secrets_txt)
- os.umask(old_umask)
-
- return c
-
-
-def apply(c):
- if c == None:
- if os.path.exists(pidfile):
- _accel_cmd('shutdown hard')
- if os.path.exists(pidfile):
- os.remove(pidfile)
- return None
-
- if not os.path.exists(pidfile):
- ret = run(f'/usr/sbin/accel-pppd -c {l2tp_conf} -p {pidfile} -d')
- chk_con()
- if ret != 0 and os.path.exists(pidfile):
- os.remove(pidfile)
- raise ConfigError('accel-pppd failed to start')
- else:
- # if gw ip changes, only restart doesn't work
- _accel_cmd('restart')
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py
index 3daa892d7..fde7dc521 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -22,7 +22,7 @@ import re
import syslog as sl
from vyos.config import Config
-from vyos.util import run
+from vyos.util import call
from vyos import ConfigError
arp_cmd = '/usr/sbin/arp'
@@ -82,12 +82,12 @@ def generate(c):
def apply(c):
for ip_addr in c['remove']:
sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr)
- run(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
+ call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
for ip_addr in c['update']:
sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr])
updated = c['update'][ip_addr]
- run(f'{arp_cmd} -s {ip_addr} {updated}')
+ call(f'{arp_cmd} -s {ip_addr} {updated}')
if __name__ == '__main__':
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py
index f6d90776c..a3bc76ef8 100755
--- a/src/conf_mode/bcast_relay.py
+++ b/src/conf_mode/bcast_relay.py
@@ -19,12 +19,11 @@ import fnmatch
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/udp-broadcast-relay'
@@ -112,11 +111,6 @@ def generate(relay):
if relay is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'bcast-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
config_dir = os.path.dirname(config_file)
config_filename = os.path.basename(config_file)
active_configs = []
@@ -146,16 +140,13 @@ def generate(relay):
# configuration filename contains instance id
file = config_file + str(r['id'])
- tmpl = env.get_template('udp-broadcast-relay.tmpl')
- config_text = tmpl.render(r)
- with open(file, 'w') as f:
- f.write(config_text)
+ render(file, 'bcast-relay/udp-broadcast-relay.tmpl', r)
return None
def apply(relay):
# first stop all running services
- run('sudo systemctl stop udp-broadcast-relay@{1..99}')
+ call('systemctl stop udp-broadcast-relay@{1..99}.service')
if (relay is None) or relay['disabled']:
return None
@@ -165,7 +156,7 @@ def apply(relay):
# Don't start individual instance when it's disabled
if r['disabled']:
continue
- run('sudo systemctl start udp-broadcast-relay@{0}'.format(r['id']))
+ call('systemctl start udp-broadcast-relay@{0}.service'.format(r['id']))
return None
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 1d6d4c6e3..ce0e01308 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -16,15 +16,14 @@
import os
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.template import render
+from vyos.util import call
from vyos import ConfigError
-from vyos.util import run
-config_file = r'/etc/default/isc-dhcp-relay'
+config_file = r'/run/dhcp-relay/dhcp.conf'
default_config_data = {
'interface': [],
@@ -96,28 +95,25 @@ def verify(relay):
def generate(relay):
# bail out early - looks like removal from running config
- if relay is None:
+ if not relay:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('config.tmpl')
- config_text = tmpl.render(relay)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcp-relay/config.tmpl', relay)
return None
def apply(relay):
- if relay is not None:
- run('sudo systemctl restart isc-dhcp-relay.service')
+ if relay:
+ call('systemctl restart isc-dhcp-relay.service')
else:
# DHCP relay support is removed in the commit
- run('sudo systemctl stop isc-dhcp-relay.service')
- os.unlink(config_file)
+ call('systemctl stop isc-dhcp-relay.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
return None
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 69aebe2f4..da01f16eb 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -17,25 +17,19 @@
import os
from ipaddress import ip_address, ip_network
-from jinja2 import FileSystemLoader, Environment
from socket import inet_ntoa
from struct import pack
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_subnet_connected
from vyos import ConfigError
-from vyos.util import run
+from vyos.template import render
+from vyos.util import call, chown
-
-config_file = r'/etc/dhcp/dhcpd.conf'
-lease_file = r'/config/dhcpd.leases'
-pid_file = r'/var/run/dhcpd.pid'
-daemon_config_file = r'/etc/default/isc-dhcpv4-server'
+config_file = r'/run/dhcp-server/dhcpd.conf'
default_config_data = {
- 'lease_file': lease_file,
'disabled': False,
'ddns_enable': False,
'global_parameters': [],
@@ -451,7 +445,7 @@ def get_config():
return dhcp
def verify(dhcp):
- if (dhcp is None) or (dhcp['disabled'] is True):
+ if not dhcp or dhcp['disabled']:
return None
# If DHCP is enabled we need one share-network
@@ -597,49 +591,29 @@ def verify(dhcp):
return None
def generate(dhcp):
- if dhcp is None:
- return None
-
- if dhcp['disabled'] is True:
- print('Warning: DHCP server will be deactivated because it is disabled')
+ if not dhcp or dhcp['disabled']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
- tmpl = env.get_template('dhcpd.conf.tmpl')
- config_text = tmpl.render(dhcp)
# Please see: https://phabricator.vyos.net/T1129 for quoting of the raw parameters
# we can pass to ISC DHCPd
- config_text = config_text.replace("&quot;",'"')
-
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render(dhcp)
- with open(daemon_config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ formater=lambda _: _.replace("&quot;", '"'))
return None
def apply(dhcp):
- if (dhcp is None) or dhcp['disabled']:
+ if not dhcp or dhcp['disabled']:
# DHCP server is removed in the commit
- run('sudo systemctl stop isc-dhcpv4-server.service')
+ call('systemctl stop isc-dhcp-server.service')
if os.path.exists(config_file):
os.unlink(config_file)
- if os.path.exists(daemon_config_file):
- os.unlink(daemon_config_file)
- else:
- # If our file holding DHCP leases does yet not exist - create it
- if not os.path.exists(lease_file):
- os.mknod(lease_file)
-
- run('sudo systemctl restart isc-dhcpv4-server.service')
+ return None
+ call('systemctl restart isc-dhcp-server.service')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index a67deb6c7..cb5a4bbfb 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -18,15 +18,13 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
-
-config_file = r'/etc/default/isc-dhcpv6-relay'
+config_file = r'/run/dhcp-relay/dhcpv6.conf'
default_config_data = {
'listen_addr': [],
@@ -86,25 +84,22 @@ def generate(relay):
if relay is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('config.tmpl')
- config_text = tmpl.render(relay)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcpv6-relay/config.tmpl', relay)
return None
def apply(relay):
if relay is not None:
- run('sudo systemctl restart isc-dhcpv6-relay.service')
+ call('systemctl restart isc-dhcp-relay6.service')
else:
# DHCPv6 relay support is removed in the commit
- run('sudo systemctl stop isc-dhcpv6-relay.service')
- os.unlink(config_file)
+ call('systemctl stop isc-dhcp-relay6.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
return None
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 003e80915..94a307826 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -19,22 +19,16 @@ import ipaddress
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.template import render
+from vyos.util import call
from vyos.validate import is_subnet_connected
from vyos import ConfigError
-from vyos.util import run
-
-config_file = r'/etc/dhcp/dhcpdv6.conf'
-lease_file = r'/config/dhcpdv6.leases'
-pid_file = r'/var/run/dhcpdv6.pid'
-daemon_config_file = r'/etc/default/isc-dhcpv6-server'
+config_file = r'/run/dhcp-server/dhcpdv6.conf'
default_config_data = {
- 'lease_file': lease_file,
'preference': '',
'disabled': False,
'shared_network': []
@@ -222,10 +216,7 @@ def get_config():
return dhcpv6
def verify(dhcpv6):
- if dhcpv6 is None:
- return None
-
- if dhcpv6['disabled']:
+ if not dhcpv6 or dhcpv6['disabled']:
return None
# If DHCP is enabled we need one share-network
@@ -337,44 +328,25 @@ def verify(dhcpv6):
return None
def generate(dhcpv6):
- if dhcpv6 is None:
- return None
-
- if dhcpv6['disabled']:
- print('Warning: DHCPv6 server will be deactivated because it is disabled')
+ if not dhcpv6 or dhcpv6['disabled']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('dhcpdv6.conf.tmpl')
- config_text = tmpl.render(dhcpv6)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render(dhcpv6)
- with open(daemon_config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcpv6-server/dhcpdv6.conf.tmpl', dhcpv6)
return None
def apply(dhcpv6):
- if (dhcpv6 is None) or dhcpv6['disabled']:
+ if not dhcpv6 or dhcpv6['disabled']:
# DHCP server is removed in the commit
- run('sudo systemctl stop isc-dhcpv6-server.service')
+ call('systemctl stop isc-dhcp-server6.service')
if os.path.exists(config_file):
os.unlink(config_file)
- if os.path.exists(daemon_config_file):
- os.unlink(daemon_config_file)
- else:
- # If our file holding DHCPv6 leases does yet not exist - create it
- if not os.path.exists(lease_file):
- os.mknod(lease_file)
- run('sudo systemctl restart isc-dhcpv6-server.service')
+ call('systemctl restart isc-dhcp-server6.service')
return None
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 5dc599425..567dfa4b3 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -19,20 +19,19 @@ import argparse
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.hostsd_client import Client as hostsd_client
from vyos.util import wait_for_commit_lock
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
parser = argparse.ArgumentParser()
parser.add_argument("--dhclient", action="store_true",
help="Started from dhclient-script")
-config_file = r'/etc/powerdns/recursor.conf'
+config_file = r'/run/powerdns/recursor.conf'
default_config_data = {
'allow_from': [],
@@ -153,25 +152,21 @@ def generate(dns):
if dns is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dns-forwarding')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
+ dirname = os.path.dirname(config_file)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
- tmpl = env.get_template('recursor.conf.tmpl')
- config_text = tmpl.render(dns)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'dns-forwarding/recursor.conf.tmpl', dns, trim_blocks=True)
return None
def apply(dns):
if dns is None:
# DNS forwarding is removed in the commit
- run("systemctl stop pdns-recursor")
+ call("systemctl stop pdns-recursor.service")
if os.path.isfile(config_file):
os.unlink(config_file)
else:
- run("systemctl restart pdns-recursor")
+ call("systemctl restart pdns-recursor.service")
if __name__ == '__main__':
args = parser.parse_args()
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index b9163f7b3..038f77cf9 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -18,18 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from stat import S_IRUSR, S_IWUSR
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
-
-config_file = r'/etc/ddclient/ddclient.conf'
-cache_file = r'/var/cache/ddclient/ddclient.cache'
-pid_file = r'/var/run/ddclient/ddclient.pid'
+config_file = r'/run/ddclient/ddclient.conf'
# Mapping of service name to service protocol
default_service_protocol = {
@@ -48,9 +44,7 @@ default_service_protocol = {
default_config_data = {
'interfaces': [],
- 'cache_file': cache_file,
- 'deleted': False,
- 'pid_file': pid_file
+ 'deleted': False
}
def get_config():
@@ -221,28 +215,13 @@ def verify(dyndns):
def generate(dyndns):
# bail out early - looks like removal from running config
if dyndns['deleted']:
- if os.path.exists(config_file):
- os.unlink(config_file)
-
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dynamic-dns')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- dirname = os.path.dirname(dyndns['pid_file'])
- if not os.path.exists(dirname):
- os.mkdir(dirname)
-
dirname = os.path.dirname(config_file)
if not os.path.exists(dirname):
os.mkdir(dirname)
- tmpl = env.get_template('ddclient.conf.tmpl')
- config_text = tmpl.render(dyndns)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
# Config file must be accessible only by its owner
os.chmod(config_file, S_IRUSR | S_IWUSR)
@@ -250,18 +229,13 @@ def generate(dyndns):
return None
def apply(dyndns):
- if os.path.exists(dyndns['cache_file']):
- os.unlink(dyndns['cache_file'])
-
- if os.path.exists('/etc/ddclient.conf'):
- os.unlink('/etc/ddclient.conf')
-
if dyndns['deleted']:
- run('/etc/init.d/ddclient stop')
- if os.path.exists(dyndns['pid_file']):
- os.unlink(dyndns['pid_file'])
+ call('systemctl stop ddclient.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
+
else:
- run('/etc/init.d/ddclient restart')
+ call('systemctl restart ddclient.service')
return None
diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py
index 90f004bc4..0b800f48f 100755
--- a/src/conf_mode/firewall_options.py
+++ b/src/conf_mode/firewall_options.py
@@ -21,7 +21,7 @@ import copy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -87,19 +87,19 @@ def apply(tcp):
target = 'VYOS_FW_OPTIONS'
# always cleanup iptables
- run('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- run('iptables --table mangle --flush {} >&/dev/null'.format(target))
- run('iptables --table mangle --delete-chain {} >&/dev/null'.format(target))
+ call('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
+ call('iptables --table mangle --flush {} >&/dev/null'.format(target))
+ call('iptables --table mangle --delete-chain {} >&/dev/null'.format(target))
# always cleanup ip6tables
- run('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --flush {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --flush {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target))
# Setup new iptables rules
if tcp['new_chain4']:
- run('iptables --table mangle --new-chain {} >&/dev/null'.format(target))
- run('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
+ call('iptables --table mangle --new-chain {} >&/dev/null'.format(target))
+ call('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
for opts in tcp['intf_opts']:
intf = opts['intf']
@@ -111,13 +111,13 @@ def apply(tcp):
# adjust TCP MSS per interface
if mss:
- run('iptables --table mangle --append {} --out-interface {} --protocol tcp ' \
+ call('iptables --table mangle --append {} --out-interface {} --protocol tcp '
'--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
# Setup new ip6tables rules
if tcp['new_chain6']:
- run('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
for opts in tcp['intf_opts']:
intf = opts['intf']
@@ -129,7 +129,7 @@ def apply(tcp):
# adjust TCP MSS per interface
if mss:
- run('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
+ call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
'--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
return None
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 1008f3fae..1354488ac 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -16,17 +16,18 @@
import os
import re
+from sys import exit
import ipaddress
from ipaddress import ip_address
from jinja2 import FileSystemLoader, Environment
-from sys import exit
+from vyos.ifconfig import Section
from vyos.ifconfig import Interface
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import cmd
+from vyos.render import render
# default values
@@ -60,7 +61,7 @@ def _sflow_default_agentip(config):
return config.return_value('protocols ospfv3 parameters router-id')
# if router-id was not found, use first available ip of any interface
- for iface in Interface.listing():
+ for iface in Section.interfaces():
for address in Interface(iface).get_addr():
# return an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -82,7 +83,7 @@ def _iptables_get_nflog():
for iptables_variant in ['iptables', 'ip6tables']:
# run iptables, save output and split it by lines
iptables_command = "sudo {0} -t {1} -S {2}".format(iptables_variant, iptables_nflog_table, iptables_nflog_chain)
- cmd(iptables_command, universal_newlines=True, message='Failed to get flows list')
+ cmd(iptables_command, message='Failed to get flows list')
iptables_out = stdout.splitlines()
# parse each line and add information to list
@@ -234,7 +235,7 @@ def verify(config):
# check that all configured interfaces exists in the system
for iface in config['interfaces']:
- if not iface in Interface.listing():
+ if not iface in Section.interfaces():
# chnged from error to warning to allow adding dynamic interfaces and interface templates
# raise ConfigError("The {} interface is not presented in the system".format(iface))
print("Warning: the {} interface is not presented in the system".format(iface))
@@ -262,7 +263,7 @@ def verify(config):
# check if configured sFlow agent-id exist in the system
agent_id_presented = None
- for iface in Interface.listing():
+ for iface in Section.interfaces():
for address in Interface(iface).get_addr():
# check an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -334,16 +335,10 @@ def generate(config):
timeout_string = "{}:{}={}".format(timeout_string, timeout_type, timeout_value)
config['netflow']['timeout_string'] = timeout_string
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'netflow')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- # Generate daemon configs
- tmpl = env.get_template('uacctd.conf.tmpl')
- config_text = tmpl.render(templatecfg = config, snaplen = default_captured_packet_size)
- with open(uacctd_conf_path, 'w') as file:
- file.write(config_text)
+ render(uacctd_conf_path, 'netflow/uacctd.conf.tmpl', {
+ 'templatecfg': config,
+ 'snaplen': default_captured_packet_size,
+ })
def apply(config):
@@ -351,9 +346,9 @@ def apply(config):
command = None
# Check if flow-accounting was removed and define command
if not config['flow-accounting-configured']:
- command = '/usr/bin/sudo /bin/systemctl stop uacctd'
+ command = 'systemctl stop uacctd.service'
else:
- command = '/usr/bin/sudo /bin/systemctl restart uacctd'
+ command = 'systemctl restart uacctd.service'
# run command to start or stop flow-accounting
cmd(command, raising=ConfigError, message='Failed to start/stop flow-accounting')
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 690d1e030..dd5819f9f 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -33,7 +33,9 @@ import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import run
default_config_data = {
@@ -157,21 +159,21 @@ def apply(config):
# rsyslog runs into a race condition at boot time with systemd
# restart rsyslog only if the hostname changed.
hostname_old = cmd('hostnamectl --static')
- cmd(f'hostnamectl set-hostname --static {hostname_new}')
+ call(f'hostnamectl set-hostname --static {hostname_new}')
# Restart services that use the hostname
if hostname_new != hostname_old:
- run("systemctl restart rsyslog.service")
+ call("systemctl restart rsyslog.service")
# If SNMP is running, restart it too
- ret = run("pgrep snmpd > /dev/null")
+ ret = run("pgrep snmpd")
if ret == 0:
- run("systemctl restart snmpd.service")
+ call("systemctl restart snmpd.service")
# restart pdns if it is used
- ret = run('/usr/bin/rec_control ping >/dev/null 2>&1')
+ ret = run('/usr/bin/rec_control ping')
if ret == 0:
- run('/etc/init.d/pdns-recursor restart >/dev/null')
+ call('systemctl restart pdns-recursor.service')
return None
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 91b8aa34b..26f4aea7f 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -24,7 +24,8 @@ from copy import deepcopy
import vyos.defaults
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
config_file = '/etc/vyos/http-api.conf'
@@ -91,9 +92,9 @@ def generate(http_api):
def apply(http_api):
if http_api is not None:
- run('sudo systemctl restart vyos-http-api.service')
+ call('sudo systemctl restart vyos-http-api.service')
else:
- run('sudo systemctl stop vyos-http-api.service')
+ call('sudo systemctl stop vyos-http-api.service')
for dep in dependencies:
cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError)
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 777792229..7d3a1b9cb 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -18,15 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
import vyos.defaults
import vyos.certbot_util
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = '/etc/nginx/sites-available/default'
@@ -133,26 +132,18 @@ def generate(https):
if https is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'https')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
if 'server_block_list' not in https or not https['server_block_list']:
https['server_block_list'] = [default_server_block]
- tmpl = env.get_template('nginx.default.tmpl')
- config_text = tmpl.render(https)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True)
return None
def apply(https):
if https is not None:
- run('sudo systemctl restart nginx.service')
+ call('sudo systemctl restart nginx.service')
else:
- run('sudo systemctl stop nginx.service')
+ call('sudo systemctl stop nginx.service')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index abe473530..9fa591a2c 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -18,13 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/igmpproxy.conf'
@@ -116,26 +115,17 @@ def generate(igmp_proxy):
print('Warning: IGMP Proxy will be deactivated because it is disabled')
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp-proxy')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('igmpproxy.conf.tmpl')
- config_text = tmpl.render(igmp_proxy)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy)
return None
def apply(igmp_proxy):
if igmp_proxy is None or igmp_proxy['disable']:
# IGMP Proxy support is removed in the commit
- run('sudo systemctl stop igmpproxy.service')
+ call('sudo systemctl stop igmpproxy.service')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- run('systemctl restart igmpproxy.service')
+ call('systemctl restart igmpproxy.service')
return None
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 6a002bc06..fd1f218d1 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -24,7 +24,8 @@ from vyos.ifconfig import BondIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
-from vyos.util import run, is_bridge_member
+from vyos.util import is_bridge_member
+from vyos.util import call
from vyos import ConfigError
default_config_data = {
@@ -91,7 +92,7 @@ def get_config():
if not os.path.isfile('/sys/class/net/bonding_masters'):
import syslog
syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module")
- if run('modprobe bonding max_bonds=0 miimon=250') != 0:
+ if call('modprobe bonding max_bonds=0 miimon=250') != 0:
syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module")
raise ConfigError("failed loading bonding kernel module")
@@ -398,32 +399,20 @@ def apply(bond):
# update interface description used e.g. within SNMP
b.set_alias(bond['description'])
- # get DHCP config dictionary and update values
- opt = b.get_dhcp_options()
-
if bond['dhcp_client_id']:
- opt['client_id'] = bond['dhcp_client_id']
+ b.dhcp.v4.options['client_id'] = bond['dhcp_client_id']
if bond['dhcp_hostname']:
- opt['hostname'] = bond['dhcp_hostname']
+ b.dhcp.v4.options['hostname'] = bond['dhcp_hostname']
if bond['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = bond['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- b.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = b.get_dhcpv6_options()
+ b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id']
if bond['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ b.dhcp.v6.options['dhcpv6_prm_only'] = True
if bond['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are required
- b.set_dhcpv6_options(opt)
+ b.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
b.set_link_detect(bond['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 79247ee51..93c6db97e 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -300,32 +300,20 @@ def apply(bridge):
# update interface description used e.g. within SNMP
br.set_alias(bridge['description'])
- # get DHCP config dictionary and update values
- opt = br.get_dhcp_options()
-
if bridge['dhcp_client_id']:
- opt['client_id'] = bridge['dhcp_client_id']
+ br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id']
if bridge['dhcp_hostname']:
- opt['hostname'] = bridge['dhcp_hostname']
+ br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname']
if bridge['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = bridge['dhcp_vendor_class_id']
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- br.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = br.get_dhcpv6_options()
+ br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id']
if bridge['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ br.dhcp.v6.options['dhcpv6_prm_only'] = True
if bridge['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- br.set_dhcpv6_options(opt)
+ br.dhcp.v6.options['dhcpv6_temporary'] = True
# assign/remove VRF
br.set_vrf(bridge['vrf'])
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 15e9b4185..5a977d797 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -301,32 +301,20 @@ def apply(eth):
# update interface description used e.g. within SNMP
e.set_alias(eth['description'])
- # get DHCP config dictionary and update values
- opt = e.get_dhcp_options()
-
if eth['dhcp_client_id']:
- opt['client_id'] = eth['dhcp_client_id']
+ e.dhcp.v4.options['client_id'] = eth['dhcp_client_id']
if eth['dhcp_hostname']:
- opt['hostname'] = eth['dhcp_hostname']
+ e.dhcp.v4.options['hostname'] = eth['dhcp_hostname']
if eth['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = eth['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- e.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = e.get_dhcpv6_options()
+ e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id']
if eth['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ e.dhcp.v6.options['dhcpv6_prm_only'] = True
if eth['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- e.set_dhcpv6_options(opt)
+ e.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
e.set_link_detect(eth['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 0400cb849..11ba9acdd 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -22,7 +22,8 @@ from copy import deepcopy
from vyos.config import Config
from vyos.ifconfig import L2TPv3If, Interface
from vyos import ConfigError
-from vyos.util import run, is_bridge_member
+from vyos.util import call
+from vyos.util import is_bridge_member
from netifaces import interfaces
default_config_data = {
@@ -51,7 +52,7 @@ def check_kmod():
modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6']
for module in modules:
if not os.path.exists(f'/sys/module/{module}'):
- if run(f'modprobe {module}') != 0:
+ if call(f'modprobe {module}') != 0:
raise ConfigError(f'Loading Kernel module {module} failed')
def get_config():
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index e9b40bb38..b42765586 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -17,23 +17,20 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from copy import deepcopy
-from sys import exit
-from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH
-from grp import getgrnam
-from ipaddress import ip_address,ip_network,IPv4Interface
+from sys import exit,stderr
+from ipaddress import IPv4Address,IPv4Network,summarize_address_range
from netifaces import interfaces
-from pwd import getpwnam
from time import sleep
from shutil import rmtree
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import VTunIf
-from vyos.util import process_running, cmd, is_bridge_member
+from vyos.util import call, is_bridge_member, chown, chmod_600, chmod_755
from vyos.validate import is_addr_assigned
from vyos import ConfigError
+from vyos.template import render
+
user = 'openvpn'
group = 'openvpn'
@@ -75,10 +72,14 @@ default_config_data = {
'server_domain': '',
'server_max_conn': '',
'server_dns_nameserver': [],
+ 'server_pool': False,
+ 'server_pool_start': '',
+ 'server_pool_stop': '',
+ 'server_pool_netmask': '',
'server_push_route': [],
'server_reject_unconfigured': False,
'server_subnet': '',
- 'server_topology': '',
+ 'server_topology': 'net30',
'shared_secret_file': '',
'tls': False,
'tls_auth': '',
@@ -97,32 +98,9 @@ default_config_data = {
def get_config_name(intf):
- cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
+ cfg_file = f'/run/openvpn/{intf}.conf'
return cfg_file
-def openvpn_mkdir(directory):
- # create directory on demand
- if not os.path.exists(directory):
- os.mkdir(directory)
-
- # fix permissions - corresponds to mode 755
- os.chmod(directory, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
- os.chown(directory, uid, gid)
-
-def fixup_permission(filename, permission=S_IRUSR):
- """
- Check if the given file exists and change ownershit to root/vyattacfg
- and appripriate file access permissions - default is user and group readable
- """
- if os.path.isfile(filename):
- os.chmod(filename, permission)
-
- # make file owned by root / vyattacfg
- uid = getpwnam('root').pw_uid
- gid = getgrnam('vyattacfg').gr_gid
- os.chown(filename, uid, gid)
def checkCertHeader(header, filename):
"""
@@ -139,6 +117,66 @@ def checkCertHeader(header, filename):
return False
+def getDefaultServer(network, topology, devtype):
+ """
+ Gets the default server parameters for a "server" directive.
+ Currently only IPv4 routed but may be extended to support bridged and/or IPv6 in the future.
+ Logic from openvpn's src/openvpn/helper.c.
+ Returns a dict with addresses or False if the input parameters were incorrect.
+ """
+ if not (topology and devtype):
+ return False
+
+ if not (devtype == 'tun' or devtype == 'tap'):
+ return False
+
+ if not network.prefixlen:
+ return False
+ elif (devtype == 'tun' and network.prefixlen > 29) or (devtype == 'tap' and network.prefixlen > 30):
+ return False
+
+ server = {
+ 'local': '',
+ 'remote_netmask': '',
+ 'client_remote_netmask': '',
+ 'pool_start': '',
+ 'pool_stop': '',
+ 'pool_netmask': ''
+ }
+
+ if devtype == 'tun':
+ if topology == 'net30' or topology == 'point-to-point':
+ server['local'] = network[1]
+ server['remote_netmask'] = network[2]
+ server['client_remote_netmask'] = server['local']
+
+ # pool start is 4th host IP in subnet (.4 in a /24)
+ server['pool_start'] = network[4]
+
+ if network.prefixlen == 29:
+ server['pool_stop'] = network.broadcast_address
+ else:
+ # pool end is -4 from the broadcast address (.251 in a /24)
+ server['pool_stop'] = network[-5]
+
+ elif topology == 'subnet':
+ server['local'] = network[1]
+ server['remote_netmask'] = str(network.netmask)
+ server['client_remote_netmask'] = server['remote_netmask']
+ server['pool_start'] = network[2]
+ server['pool_stop'] = network[-3]
+ server['pool_netmask'] = server['remote_netmask']
+
+ elif devtype == 'tap':
+ server['local'] = network[1]
+ server['remote_netmask'] = str(network.netmask)
+ server['client_remote_netmask'] = server['remote_netmask']
+ server['pool_start'] = network[2]
+ server['pool_stop'] = network[-2]
+ server['pool_netmask'] = server['remote_netmask']
+
+ return server
+
def get_config():
openvpn = deepcopy(default_config_data)
conf = Config()
@@ -308,10 +346,10 @@ def get_config():
# Server-mode subnet (from which client IPs are allocated)
if conf.exists('server subnet'):
- network = conf.return_value('server subnet')
- tmp = IPv4Interface(network).with_netmask
+ # server_network is used later in this function
+ server_network = IPv4Network(conf.return_value('server subnet'))
# convert the network in format: "192.0.2.0 255.255.255.0" for later use in template
- openvpn['server_subnet'] = tmp.replace(r'/', ' ')
+ openvpn['server_subnet'] = server_network.with_netmask.replace(r'/', ' ')
# Client-specific settings
for client in conf.list_nodes('server client'):
@@ -326,19 +364,6 @@ def get_config():
'remote_netmask': ''
}
- # note: with "topology subnet", this is "<ip> <netmask>".
- # with "topology p2p", this is "<ip> <our_ip>".
- if openvpn['server_topology'] == 'subnet':
- # we are only interested in the netmask portion of server_subnet
- data['remote_netmask'] = openvpn['server_subnet'].split(' ')[1]
- else:
- # we need the server subnet in format 192.0.2.0/255.255.255.0
- subnet = openvpn['server_subnet'].replace(' ', r'/')
- # get iterator over the usable hosts in the network
- tmp = ip_network(subnet).hosts()
- # OpenVPN always uses the subnets first available IP address
- data['remote_netmask'] = list(tmp)[0]
-
# Option to disable client connection
if conf.exists('disable'):
data['disable'] = True
@@ -349,13 +374,11 @@ def get_config():
# Route to be pushed to the client
for network in conf.return_values('push-route'):
- tmp = IPv4Interface(network).with_netmask
- data['push_route'].append(tmp.replace(r'/', ' '))
+ data['push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Subnet belonging to the client
for network in conf.return_values('subnet'):
- tmp = IPv4Interface(network).with_netmask
- data['subnet'].append(tmp.replace(r'/', ' '))
+ data['subnet'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Append to global client list
openvpn['client'].append(data)
@@ -363,6 +386,19 @@ def get_config():
# re-set configuration level
conf.set_level('interfaces openvpn ' + openvpn['intf'])
+ # Server client IP pool
+ if conf.exists('server client-ip-pool'):
+ openvpn['server_pool'] = True
+
+ if conf.exists('server client-ip-pool start'):
+ openvpn['server_pool_start'] = conf.return_value('server client-ip-pool start')
+
+ if conf.exists('server client-ip-pool stop'):
+ openvpn['server_pool_stop'] = conf.return_value('server client-ip-pool stop')
+
+ if conf.exists('server client-ip-pool netmask'):
+ openvpn['server_pool_netmask'] = conf.return_value('server client-ip-pool netmask')
+
# DNS suffix to be pushed to all clients
if conf.exists('server domain-name'):
openvpn['server_domain'] = conf.return_value('server domain-name')
@@ -378,8 +414,7 @@ def get_config():
# Route to be pushed to all clients
if conf.exists('server push-route'):
for network in conf.return_values('server push-route'):
- tmp = IPv4Interface(network).with_netmask
- openvpn['server_push_route'].append(tmp.replace(r'/', ' '))
+ openvpn['server_push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Reject connections from clients that are not explicitly configured
if conf.exists('server reject-unconfigured-clients'):
@@ -428,6 +463,7 @@ def get_config():
# Minimum required TLS version
if conf.exists('tls tls-version-min'):
openvpn['tls_version_min'] = conf.return_value('tls tls-version-min')
+ openvpn['tls'] = True
if conf.exists('shared-secret-key-file'):
openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file')
@@ -440,6 +476,26 @@ def get_config():
if not openvpn['tls_dh'] and openvpn['tls_key'] and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
openvpn['tls_dh'] = 'none'
+ # Set defaults where necessary.
+ # If any of the input parameters are missing or wrong,
+ # this will return False and no defaults will be set.
+ default_server = getDefaultServer(server_network, openvpn['server_topology'], openvpn['type'])
+ if default_server:
+ # server-bridge doesn't require a pool so don't set defaults for it
+ if not openvpn['bridge_member']:
+ openvpn['server_pool'] = True
+ if not openvpn['server_pool_start']:
+ openvpn['server_pool_start'] = default_server['pool_start']
+
+ if not openvpn['server_pool_stop']:
+ openvpn['server_pool_stop'] = default_server['pool_stop']
+
+ if not openvpn['server_pool_netmask']:
+ openvpn['server_pool_netmask'] = default_server['pool_netmask']
+
+ for client in openvpn['client']:
+ client['remote_netmask'] = default_server['client_remote_netmask']
+
return openvpn
def verify(openvpn):
@@ -489,7 +545,11 @@ def verify(openvpn):
# OpenVPN site-to-site - VERIFY
#
if openvpn['mode'] == 'site-to-site':
- if not (openvpn['local_address'] or openvpn['bridge_member']):
+ if openvpn['ncp_ciphers']:
+ raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
+
+ if openvpn['mode'] == 'site-to-site' and not openvpn['bridge_member']:
+ if not openvpn['local_address']:
raise ConfigError('Must specify "local-address" or "bridge member interface"')
for host in openvpn['remote_host']:
@@ -506,15 +566,10 @@ def verify(openvpn):
if openvpn['local_address'] == openvpn['local_host']:
raise ConfigError('"local-address" cannot be the same as "local-host"')
- if openvpn['ncp_ciphers']:
- raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
-
else:
+ # checks for client-server or site-to-site bridged
if openvpn['local_address'] or openvpn['remote_address']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server mode')
-
- elif openvpn['bridge_member']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in bridge mode')
+ raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server or bridge mode')
#
# OpenVPN server mode - VERIFY
@@ -535,9 +590,41 @@ def verify(openvpn):
if not openvpn['tls_dh'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode')
- if not openvpn['server_subnet']:
+ if openvpn['server_subnet']:
+ subnet = IPv4Network(openvpn['server_subnet'].replace(' ', '/'))
+
+ if openvpn['type'] == 'tun' and subnet.prefixlen > 29:
+ raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported')
+ elif openvpn['type'] == 'tap' and subnet.prefixlen > 30:
+ raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported')
+
+ for client in openvpn['client']:
+ if client['ip'] and not IPv4Address(client['ip']) in subnet:
+ raise ConfigError(f'Client IP "{client["ip"]}" not in server subnet "{subnet}"')
+
+ else:
if not openvpn['bridge_member']:
- raise ConfigError('Must specify "server subnet" option in server mode')
+ raise ConfigError('Must specify "server subnet" or "bridge member interface" in server mode')
+
+
+ if openvpn['server_pool']:
+ if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']):
+ raise ConfigError('Server client-ip-pool requires both start and stop addresses in bridged mode')
+ else:
+ v4PoolStart = IPv4Address(openvpn['server_pool_start'])
+ v4PoolStop = IPv4Address(openvpn['server_pool_stop'])
+ if v4PoolStart > v4PoolStop:
+ raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+ if (int(v4PoolStop) - int(v4PoolStart) >= 65536):
+ raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop}], maximum is 65536 addresses.')
+
+ v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop))
+ for client in openvpn['client']:
+ if client['ip']:
+ for v4PoolNet in v4PoolNets:
+ if IPv4Address(client['ip']) in v4PoolNet:
+ print(f'Warning: Client "{client["name"]}" IP {client["ip"]} is in server IP pool, it is not reserved for this client.',
+ file=stderr)
else:
# checks for both client and site-to-site go here
@@ -665,143 +752,98 @@ def verify(openvpn):
if not openvpn['auth_pass']:
raise ConfigError('Password for authentication is missing')
- #
- # Client
- #
- subnet = openvpn['server_subnet'].replace(' ', '/')
- for client in openvpn['client']:
- if client['ip'] and not ip_address(client['ip']) in ip_network(subnet):
- raise ConfigError('Client IP "{}" not in server subnet "{}'.format(client['ip'], subnet))
-
return None
def generate(openvpn):
if openvpn['deleted'] or openvpn['disable']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'openvpn')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
interface = openvpn['intf']
directory = os.path.dirname(get_config_name(interface))
- # we can't know which clients were deleted, remove all client configs
- if os.path.isdir(os.path.join(directory, 'ccd', interface)):
- rmtree(os.path.join(directory, 'ccd', interface), ignore_errors=True)
+ # we can't know in advance which clients have been,
+ # remove all client configs
+ ccd_dir = os.path.join(directory, 'ccd', interface)
+ if os.path.isdir(ccd_dir):
+ rmtree(ccd_dir, ignore_errors=True)
# create config directory on demand
- openvpn_mkdir(directory)
- # create status directory on demand
- openvpn_mkdir(directory + '/status')
- # create client config dir on demand
- openvpn_mkdir(directory + '/ccd')
- # crete client config dir per interface on demand
- openvpn_mkdir(directory + '/ccd/' + interface)
+ directories = []
+ directories.append(f'{directory}/status')
+ directories.append(f'{directory}/ccd/{interface}')
+ for onedir in directories:
+ if not os.path.exists(onedir):
+ os.makedirs(onedir, 0o755)
+ chown(onedir, user, group)
# Fix file permissons for keys
- fixup_permission(openvpn['shared_secret_file'])
- fixup_permission(openvpn['tls_key'])
+ fix_permissions = []
+ fix_permissions.append(openvpn['shared_secret_file'])
+ fix_permissions.append(openvpn['tls_key'])
# Generate User/Password authentication file
+ user_auth_file = f'/tmp/openvpn-{interface}-pw'
if openvpn['auth']:
- auth_file = '/tmp/openvpn-{}-pw'.format(interface)
- with open(auth_file, 'w') as f:
+ with open(user_auth_file, 'w') as f:
f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass']))
-
- fixup_permission(auth_file)
+ # also change permission on auth file
+ fix_permissions.append(user_auth_file)
else:
# delete old auth file if present
- if os.path.isfile('/tmp/openvpn-{}-pw'.format(interface)):
- os.remove('/tmp/openvpn-{}-pw'.format(interface))
-
- # get numeric uid/gid
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
+ if os.path.isfile(user_auth_file):
+ os.remove(user_auth_file)
# Generate client specific configuration
for client in openvpn['client']:
- client_file = directory + '/ccd/' + interface + '/' + client['name']
- tmpl = env.get_template('client.conf.tmpl')
- client_text = tmpl.render(client)
- with open(client_file, 'w') as f:
- f.write(client_text)
- os.chown(client_file, uid, gid)
-
- tmpl = env.get_template('server.conf.tmpl')
- config_text = tmpl.render(openvpn)
+ client_file = os.path.join(ccd_dir, client['name'])
+ render(client_file, 'openvpn/client.conf.tmpl', client)
+ chown(client_file, user, group)
+
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
- config_text = config_text.replace("&quot;",'"')
- with open(get_config_name(interface), 'w') as f:
- f.write(config_text)
- os.chown(get_config_name(interface), uid, gid)
+ render(get_config_name(interface), 'openvpn/server.conf.tmpl', openvpn,
+ formater=lambda _: _.replace("&quot;", '"'))
+ chown(get_config_name(interface), user, group)
+
+ # Fixup file permissions
+ for file in fix_permissions:
+ chmod_600(file)
return None
def apply(openvpn):
- pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf'])
-
- # Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the
- # service as the configuration is not re-read. Stop daemon only if it's
- # running - it could have died or killed by someone evil
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- cmd(command)
-
- # cleanup old PID file
- if os.path.isfile(pidfile):
- os.remove(pidfile)
+ interface = openvpn['intf']
+ call(f'systemctl stop openvpn@{interface}.service')
# Do some cleanup when OpenVPN is disabled/deleted
if openvpn['deleted'] or openvpn['disable']:
# cleanup old configuration file
- if os.path.isfile(get_config_name(openvpn['intf'])):
- os.remove(get_config_name(openvpn['intf']))
+ if os.path.isfile(get_config_name(interface)):
+ os.remove(get_config_name(interface))
# cleanup client config dir
- directory = os.path.dirname(get_config_name(openvpn['intf']))
- if os.path.isdir(os.path.join(directory, 'ccd', openvpn['intf'])):
- rmtree(os.path.join(directory, 'ccd', openvpn['intf']), ignore_errors=True)
-
- # cleanup auth file
- if os.path.isfile('/tmp/openvpn-{}-pw'.format(openvpn['intf'])):
- os.remove('/tmp/openvpn-{}-pw'.format(openvpn['intf']))
+ directory = os.path.dirname(get_config_name(interface))
+ ccd_dir = os.path.join(directory, 'ccd', interface)
+ if os.path.isdir(ccd_dir):
+ rmtree(ccd_dir, ignore_errors=True)
return None
# On configuration change we need to wait for the 'old' interface to
# vanish from the Kernel, if it is not gone, OpenVPN will report:
# ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
- while openvpn['intf'] in interfaces():
+ while interface in interfaces():
sleep(0.250) # 250ms
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- command += ' --exec /usr/sbin/openvpn'
- # now pass arguments to openvpn binary
- command += ' --'
- command += ' --daemon openvpn-' + openvpn['intf']
- command += ' --config ' + get_config_name(openvpn['intf'])
-
- # execute assembled command
- cmd(command)
+ call(f'systemctl start openvpn@{interface}.service')
# better late then sorry ... but we can only set interface alias after
# OpenVPN has been launched and created the interface
cnt = 0
- while openvpn['intf'] not in interfaces():
+ while interface not in interfaces():
# If VPN tunnel can't be established because the peer/server isn't
# (temporarily) available, the vtun interface never becomes registered
# with the kernel, and the commit would hang if there is no bail out
@@ -816,7 +858,7 @@ def apply(openvpn):
try:
# we need to catch the exception if the interface is not up due to
# reason stated above
- o = VTunIf(openvpn['intf'])
+ o = VTunIf(interface)
# update interface description used e.g. within SNMP
o.set_alias(openvpn['description'])
# IPv6 address autoconfiguration
@@ -834,7 +876,7 @@ def apply(openvpn):
# TAP interface needs to be brought up explicitly
if openvpn['type'] == 'tap':
if not openvpn['disable']:
- VTunIf(openvpn['intf']).set_admin_state('up')
+ VTunIf(interface).set_admin_state('up')
return None
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 407547175..f942b7d2f 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -18,14 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import Interface
-from vyos.util import chown_file, chmod_x, cmd
+from vyos.util import chown, chmod_755, cmd
from vyos import ConfigError
+from vyos.template import render
+
default_config_data = {
'access_concentrator': '',
@@ -155,14 +155,12 @@ def verify(pppoe):
if vrf_name and vrf_name not in interfaces():
raise ConfigError(f'VRF {vrf_name} does not exist')
+ if pppoe['on_demand'] and pppoe['vrf']:
+ raise ConfigError('On-demand dialing and VRF can not be used at the same time')
+
return None
def generate(pppoe):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "pppoe")
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# set up configuration file path variables where our templates will be
# rendered into
intf = pppoe['intf']
@@ -192,40 +190,26 @@ def generate(pppoe):
else:
# Create PPP configuration files
- tmpl = env.get_template('peer.tmpl')
- config_text = tmpl.render(pppoe)
- with open(config_pppoe, 'w') as f:
- f.write(config_text)
-
+ render(config_pppoe, 'pppoe/peer.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-pre-up.d
- tmpl = env.get_template('ip-pre-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_pre_up, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-up.d
- tmpl = env.get_template('ip-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ip_up, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-down.d
- tmpl = env.get_template('ip-down.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ip_down, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ipv6-up.d
- tmpl = env.get_template('ipv6-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ipv6_up, 'w') as f:
- f.write(config_text)
+ render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl',
+ pppoe, trim_blocks=True)
# make generated script file executable
- chmod_x(script_pppoe_pre_up)
- chmod_x(script_pppoe_ip_up)
- chmod_x(script_pppoe_ip_down)
- chmod_x(script_pppoe_ipv6_up)
+ chmod_755(script_pppoe_pre_up)
+ chmod_755(script_pppoe_ip_up)
+ chmod_755(script_pppoe_ip_down)
+ chmod_755(script_pppoe_ipv6_up)
return None
@@ -240,7 +224,7 @@ def apply(pppoe):
cmd(f'systemctl start ppp@{intf}.service')
# make logfile owned by root / vyattacfg
- chown_file(pppoe['logfile'], 'root', 'vyattacfg')
+ chown(pppoe['logfile'], 'root', 'vyattacfg')
return None
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 50b5a12a0..655006146 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -51,8 +51,8 @@ default_config_data = {
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
'intf': '',
- 'link': '',
- 'link_changed': False,
+ 'source_interface': '',
+ 'source_interface_changed': False,
'mac': '',
'mode': 'private',
'vif_s': [],
@@ -166,12 +166,12 @@ def get_config():
if conf.exists('ipv6 dup-addr-detect-transmits'):
peth['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
- # Lower link device
- if conf.exists(['link']):
- peth['link'] = conf.return_value(['link'])
- tmp = conf.return_effective_value(['link'])
- if tmp != peth['link']:
- peth['link_changed'] = True
+ # Physical interface
+ if conf.exists(['source-interface']):
+ peth['source_interface'] = conf.return_value(['source-interface'])
+ tmp = conf.return_effective_value(['source-interface'])
+ if tmp != peth['source_interface']:
+ peth['source_interface_changed'] = True
# Media Access Control (MAC) address
if conf.exists(['mac']):
@@ -227,10 +227,10 @@ def verify(peth):
'is a member of bridge "{1}"!'.format(interface, bridge))
return None
- if not peth['link']:
+ if not peth['source_interface']:
raise ConfigError('Link device must be set for virtual ethernet {}'.format(peth['intf']))
- if not peth['link'] in interfaces():
+ if not peth['source_interface'] in interfaces():
raise ConfigError('Pseudo-ethernet source interface does not exist')
vrf_name = peth['vrf']
@@ -253,12 +253,12 @@ def apply(peth):
p.remove()
return None
- elif peth['link_changed']:
+ elif peth['source_interface_changed']:
# Check if MACVLAN interface already exists. Parameters like the
- # underlaying link device can not be changed on the fly and the
- # interface needs to be recreated from the bottom.
+ # underlaying source-interface device can not be changed on the fly
+ # and the interface needs to be recreated from the bottom.
#
- # link_changed also means - the interface was not present in the
+ # source_interface_changed also means - the interface was not present in the
# beginning and is newly created
if peth['intf'] in interfaces():
p = MACVLANIf(peth['intf'])
@@ -269,7 +269,7 @@ def apply(peth):
conf = deepcopy(MACVLANIf.get_config())
# Assign MACVLAN instance configuration parameters to config dict
- conf['link'] = peth['link']
+ conf['source_interface'] = peth['source_interface']
conf['mode'] = peth['mode']
# It is safe to "re-create" the interface always, there is a sanity check
@@ -281,32 +281,20 @@ def apply(peth):
# update interface description used e.g. within SNMP
p.set_alias(peth['description'])
- # get DHCP config dictionary and update values
- opt = p.get_dhcp_options()
-
if peth['dhcp_client_id']:
- opt['client_id'] = peth['dhcp_client_id']
+ p.dhcp.v4.options['client_id'] = peth['dhcp_client_id']
if peth['dhcp_hostname']:
- opt['hostname'] = peth['dhcp_hostname']
+ p.dhcp.v4.options['hostname'] = peth['dhcp_hostname']
if peth['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = peth['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- p.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = p.get_dhcpv6_options()
+ p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id']
if peth['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ p.dhcp.v6.options['dhcpv6_prm_only'] = True
if peth['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- p.set_dhcpv6_options(opt)
+ p.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
p.set_link_detect(peth['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 646e61c53..c51048aeb 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -26,21 +26,68 @@ from vyos.ifconfig.afi import IP4, IP6
from vyos.configdict import list_diff
from vyos.validate import is_ipv4, is_ipv6
from vyos import ConfigError
+from vyos.dicts import FixedDict
+class ConfigurationState(Config):
+ """
+ The current API require a dict to be generated by get_config()
+ which is then consumed by verify(), generate() and apply()
-class FixedDict(dict):
- def __init__ (self, **options):
- self._allowed = options.keys()
- super().__init__(**options)
+ ConfiguartionState is an helper class wrapping Config and providing
+ an common API to this dictionary structure
- def __setitem__ (self, k, v):
- if k not in self._allowed:
- raise ConfigError(f'Option "{k}" has no defined default')
- super().__setitem__(k, v)
+ Its to_dict() function return a dictionary containing three fields,
+ each a dict, called options, changes, actions.
+ options:
+
+ contains the configuration options for the dict and its value
+ {'options': {'commment': 'test'}} will be set if
+ 'set interface dummy dum1 description test' was used and
+ the key 'commment' is used to index the description info.
+
+ changes:
+
+ per key, let us know how the data was modified using one of the action
+ a special key called 'section' is used to indicate what happened to the
+ section. for example:
+
+ 'set interface dummy dum1 description test' when no interface was setup
+ will result in the following changes
+ {'changes': {'section': 'create', 'comment': 'create'}}
+
+ on an existing interface, depending if there was a description
+ 'set interface dummy dum1 description test' will result in one of
+ {'changes': {'comment': 'create'}} (not present before)
+ {'changes': {'comment': 'static'}} (unchanged)
+ {'changes': {'comment': 'modify'}} (changed from half)
+
+ and 'delete interface dummy dummy1 description' will result in:
+ {'changes': {'comment': 'delete'}}
+
+ actions:
+
+ for each action list the configuration key which were changes
+ in our example if we added the 'description' and added an IP we would have
+ {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}}
+
+ the actions are:
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ which for each field represent how it was modified since the last commit
+ """
-class ConfigurationState (Config):
def __init__ (self, section, default):
+ """
+ initialise the class for a given configuration path:
+
+ >>> conf = ConfigurationState('interfaces ethernet eth1')
+ all further references to get_value(s) and get_effective(s)
+ will be for this part of the configuration (eth1)
+ """
super().__init__()
self.section = section
self.default = deepcopy(default)
@@ -61,6 +108,15 @@ class ConfigurationState (Config):
self.changes['section'] = 'create'
def _act(self, section):
+ """
+ Returns for a given configuration field determine what happened to it
+
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ """
if self.exists(section):
if self.exists_effective(section):
if self.return_value(section) != self.return_effective_value(section):
@@ -89,24 +145,71 @@ class ConfigurationState (Config):
self.options[name] = value
def get_value(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_value
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_value)
def get_values(self, name, key, default=None):
+ """
+ >>> conf.get_values('addresses-add', 'address')
+ will place a list made of the IP present in 'interface dummy dum1 address'
+ into the dictionnary entry 'addr' using Config.return_values
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_values)
def get_effective(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_effective_value
+ (the data in the configuration to apply)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effective_value)
def get_effectives(self, name, key, default=None):
+ """
+ >>> conf.get_effectives('addresses-add', 'address')
+ will place a list made of the IP present in 'interface ethernet eth1 address'
+ into the dictionnary entry 'addresses-add' using Config.return_effectives_value
+ (the data in the un-modified configuration)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effectives_value)
def load(self, mapping):
+ """
+ load will take a dictionary defining how we wish the configuration
+ to be parsed and apply this definition to set the data.
+
+ >>> mapping = {
+ 'addresses-add' : ('address', True, None),
+ 'comment' : ('description', False, 'auto'),
+ }
+ >>> conf.load(mapping)
+
+ mapping is a dictionary where each key represents the name we wish
+ to have (such as 'addresses-add'), with a list a content representing
+ how the data should be parsed:
+ - the configuration section name
+ such as 'address' under 'interface ethernet eth1'
+ - boolean indicating if this data can have multiple values
+ for 'address', True, as multiple IPs can be set
+ for 'description', False, as it is a single string
+ - default represent the default value if absent from the configuration
+ 'None' indicate that no default should be set if the configuration
+ does not have the configuration section
+
+ """
for local_name, (config_name, multiple, default) in mapping.items():
if multiple:
self.get_values(local_name, config_name, default)
@@ -114,12 +217,21 @@ class ConfigurationState (Config):
self.get_value(local_name, config_name, default)
def remove_default (self,*options):
+ """
+ remove all the values which were not changed from the default
+ """
for option in options:
if self.exists(option) and self_return_value(option) != self.default[option]:
continue
del self.options[option]
def to_dict (self):
+ """
+ provide a dictionary with the generated data for the configuration
+ options: the configuration value for the key
+ changes: per key how they changed from the previous configuration
+ actions: per changes all the options which were changed
+ """
# as we have to use a dict() for the API for verify and apply the options
return {
'options': self.options,
@@ -203,7 +315,8 @@ def get_class (options):
}
kls = dispatch[options['type']]
- if options['type'] == 'gre' and not options['remote']:
+ if options['type'] == 'gre' and not options['remote'] \
+ and not options['key'] and not options['multicast']:
# will use GreTapIf on GreIf deletion but it does not matter
return GRETapIf
elif options['type'] == 'sit' and options['6rd-prefix']:
@@ -471,11 +584,17 @@ def apply(conf):
if changes['section'] in 'create' and option in tunnel.options:
# it was setup at creation
continue
+ if not options[option]:
+ # remote can be set to '' and it would generate an invalide command
+ continue
tunnel.set_interface(option, options[option])
# set other interface properties
for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast',
'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
+ if not options[option]:
+ # should never happen but better safe
+ continue
tunnel.set_interface(option, options[option])
# Configure interface address(es)
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index b9bfb242a..6639a9b0d 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -42,7 +42,8 @@ default_config_data = {
'ipv6_eui64_prefix': '',
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
- 'link': '',
+ 'source_address': '',
+ 'source_interface': '',
'mtu': 1450,
'remote': '',
'remote_port': 8472, # The Linux implementation of VXLAN pre-dates
@@ -124,9 +125,13 @@ def get_config():
if conf.exists('ipv6 dup-addr-detect-transmits'):
vxlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
+ # VXLAN source address
+ if conf.exists('source-address'):
+ vxlan['source_address'] = conf.return_value('source-address')
+
# VXLAN underlay interface
- if conf.exists('link'):
- vxlan['link'] = conf.return_value('link')
+ if conf.exists('source-interface'):
+ vxlan['source_interface'] = conf.return_value('source-interface')
# Maximum Transmission Unit (MTU)
if conf.exists('mtu'):
@@ -162,18 +167,22 @@ def verify(vxlan):
print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
if vxlan['group']:
- if not vxlan['link']:
+ if not vxlan['source_interface']:
raise ConfigError('Multicast VXLAN requires an underlaying interface ')
- if not vxlan['link'] in interfaces():
+
+ if not vxlan['source_interface'] in interfaces():
raise ConfigError('VXLAN source interface does not exist')
+ if not (vxlan['group'] or vxlan['remote'] or vxlan['source_address']):
+ raise ConfigError('Group, remote or source-address must be configured')
+
if not vxlan['vni']:
raise ConfigError('Must configure VNI for VXLAN')
- if vxlan['link']:
+ if vxlan['source_interface']:
# VXLAN adds a 50 byte overhead - we need to check the underlaying MTU
# if our configured MTU is at least 50 bytes less
- underlay_mtu = int(Interface(vxlan['link']).get_mtu())
+ underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu())
if underlay_mtu < (vxlan['mtu'] + 50):
raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
'MTU is to small ({})'.format(underlay_mtu))
@@ -202,7 +211,8 @@ def apply(vxlan):
# Assign VXLAN instance configuration parameters to config dict
conf['vni'] = vxlan['vni']
conf['group'] = vxlan['group']
- conf['dev'] = vxlan['link']
+ conf['src_address'] = vxlan['source_address']
+ conf['src_interface'] = vxlan['source_interface']
conf['remote'] = vxlan['remote']
conf['port'] = vxlan['remote_port']
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 54121a6c1..8bf81c747 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -14,173 +14,181 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import sys
import os
import re
+from sys import exit
from copy import deepcopy
from netifaces import interfaces
-from vyos import ConfigError
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.util import run, is_bridge_member
from vyos.ifconfig import WireGuardIf
+from vyos.util import chown, is_bridge_member, chmod_750
+from vyos.util import call
+from vyos import ConfigError
kdir = r'/config/auth/wireguard'
+default_config_data = {
+ 'intfc': '',
+ 'address': [],
+ 'address_remove': [],
+ 'description': '',
+ 'lport': None,
+ 'deleted': False,
+ 'disable': False,
+ 'fwmark': 0x00,
+ 'mtu': 1420,
+ 'peer': [],
+ 'peer_remove': [], # stores public keys of peers to remove
+ 'pk': f'{kdir}/default/private.key',
+ 'vrf': ''
+}
+
def _check_kmod():
- if not os.path.exists('/sys/module/wireguard'):
- if run('modprobe wireguard') != 0:
- raise ConfigError("modprobe wireguard failed")
+ modules = ['wireguard']
+ for module in modules:
+ if not os.path.exists(f'/sys/module/{module}'):
+ if call(f'modprobe {module}') != 0:
+ raise ConfigError(f'Loading Kernel module {module} failed')
def _migrate_default_keys():
if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'):
- old_umask = os.umask(0o027)
location = f'{kdir}/default'
- run(f'sudo mkdir -p {location}')
- run(f'sudo chgrp vyattacfg {location}')
- run(f'sudo chmod 750 {location}')
+ if not os.path.exists(location):
+ os.makedirs(location)
+
+ chown(location, 'root', 'vyattacfg')
+ chmod_750(location)
os.rename(f'{kdir}/private.key', f'{location}/private.key')
os.rename(f'{kdir}/public.key', f'{location}/public.key')
- os.umask(old_umask)
def get_config():
- c = Config()
- if not c.exists(['interfaces', 'wireguard']):
- return None
+ conf = Config()
+ base = ['interfaces', 'wireguard']
# determine tagNode instance
if 'VYOS_TAGNODE_VALUE' not in os.environ:
raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
- dflt_cnf = {
- 'intfc': '',
- 'addr': [],
- 'addr_remove': [],
- 'descr': '',
- 'lport': None,
- 'delete': False,
- 'state': 'up',
- 'fwmark': 0x00,
- 'mtu': 1420,
- 'peer': {},
- 'peer_remove': [],
- 'pk': '{}/default/private.key'.format(kdir)
- }
-
- ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
- wg = deepcopy(dflt_cnf)
- wg['intfc'] = ifname
- wg['descr'] = ifname
-
- c.set_level(['interfaces', 'wireguard'])
-
- # interface removal state
- if not c.exists(ifname) and c.exists_effective(ifname):
- wg['delete'] = True
-
- if not wg['delete']:
- c.set_level(['interfaces', 'wireguard', ifname])
- if c.exists(['address']):
- wg['addr'] = c.return_values(['address'])
-
- # determine addresses which need to be removed
- eff_addr = c.return_effective_values(['address'])
- wg['addr_remove'] = list_diff(eff_addr, wg['addr'])
-
- # ifalias description
- if c.exists(['description']):
- wg['descr'] = c.return_value(['description'])
-
- # link state
- if c.exists(['disable']):
- wg['state'] = 'down'
-
- # local port to listen on
- if c.exists(['port']):
- wg['lport'] = c.return_value(['port'])
-
- # fwmark value
- if c.exists(['fwmark']):
- wg['fwmark'] = c.return_value(['fwmark'])
-
- # mtu
- if c.exists('mtu'):
- wg['mtu'] = c.return_value('mtu')
-
- # private key
- if c.exists(['private-key']):
- wg['pk'] = "{0}/{1}/private.key".format(
- kdir, c.return_value(['private-key']))
-
- # peer removal, wg identifies peers by its pubkey
- peer_eff = c.list_effective_nodes(['peer'])
- peer_rem = list_diff(peer_eff, c.list_nodes(['peer']))
- for p in peer_rem:
- wg['peer_remove'].append(
- c.return_effective_value(['peer', p, 'pubkey']))
-
- # peer settings
- if c.exists(['peer']):
- for p in c.list_nodes(['peer']):
- if not c.exists(['peer', p, 'disable']):
- wg['peer'].update(
- {
- p: {
- 'allowed-ips': [],
- 'address': '',
- 'port': '',
- 'pubkey': ''
- }
- }
- )
- # peer allowed-ips
- if c.exists(['peer', p, 'allowed-ips']):
- wg['peer'][p]['allowed-ips'] = c.return_values(
- ['peer', p, 'allowed-ips'])
- # peer address
- if c.exists(['peer', p, 'address']):
- wg['peer'][p]['address'] = c.return_value(
- ['peer', p, 'address'])
- # peer port
- if c.exists(['peer', p, 'port']):
- wg['peer'][p]['port'] = c.return_value(
- ['peer', p, 'port'])
- # persistent-keepalive
- if c.exists(['peer', p, 'persistent-keepalive']):
- wg['peer'][p]['persistent-keepalive'] = c.return_value(
- ['peer', p, 'persistent-keepalive'])
- # preshared-key
- if c.exists(['peer', p, 'preshared-key']):
- wg['peer'][p]['psk'] = c.return_value(
- ['peer', p, 'preshared-key'])
- # peer pubkeys
- key_eff = c.return_effective_value(['peer', p, 'pubkey'])
- key_cfg = c.return_value(['peer', p, 'pubkey'])
- wg['peer'][p]['pubkey'] = key_cfg
-
- # on a pubkey change we need to remove the pubkey first
- # peers are identified by pubkey, so key update means
- # peer removal and re-add
- if key_eff != key_cfg and key_eff != None:
- wg['peer_remove'].append(key_cfg)
-
- # if a peer is disabled, we have to exec a remove for it's pubkey
- else:
- peer_key = c.return_value(['peer', p, 'pubkey'])
- wg['peer_remove'].append(peer_key)
+ wg = deepcopy(default_config_data)
+ wg['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+
+ # Check if interface has been removed
+ if not conf.exists(base + [wg['intf']]):
+ wg['deleted'] = True
+ return wg
+
+ conf.set_level(base + [wg['intf']])
+
+ # retrieve configured interface addresses
+ if conf.exists(['address']):
+ wg['address'] = conf.return_values(['address'])
+
+ # get interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values(['address'])
+ wg['address_remove'] = list_diff(eff_addr, wg['address'])
+
+ # retrieve interface description
+ if conf.exists(['description']):
+ wg['description'] = conf.return_value(['description'])
+
+ # disable interface
+ if conf.exists(['disable']):
+ wg['disable'] = True
+
+ # local port to listen on
+ if conf.exists(['port']):
+ wg['lport'] = conf.return_value(['port'])
+
+ # fwmark value
+ if conf.exists(['fwmark']):
+ wg['fwmark'] = int(conf.return_value(['fwmark']))
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ wg['mtu'] = int(conf.return_value(['mtu']))
+
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ wg['vrf'] = conf.return_value('vrf')
+
+ # private key
+ if conf.exists(['private-key']):
+ wg['pk'] = "{0}/{1}/private.key".format(
+ kdir, conf.return_value(['private-key']))
+
+ # peer removal, wg identifies peers by its pubkey
+ peer_eff = conf.list_effective_nodes(['peer'])
+ peer_rem = list_diff(peer_eff, conf.list_nodes(['peer']))
+ for peer in peer_rem:
+ wg['peer_remove'].append(
+ conf.return_effective_value(['peer', peer, 'pubkey']))
+
+ # peer settings
+ if conf.exists(['peer']):
+ for p in conf.list_nodes(['peer']):
+ # set new config level for this peer
+ conf.set_level(base + [wg['intf'], 'peer', p])
+ peer = {
+ 'allowed-ips': [],
+ 'address': '',
+ 'name': p,
+ 'persistent_keepalive': '',
+ 'port': '',
+ 'psk': '',
+ 'pubkey': ''
+ }
+
+ # peer allowed-ips
+ if conf.exists(['allowed-ips']):
+ peer['allowed-ips'] = conf.return_values(['allowed-ips'])
+
+ # peer address
+ if conf.exists(['address']):
+ peer['address'] = conf.return_value(['address'])
+
+ # peer port
+ if conf.exists(['port']):
+ peer['port'] = conf.return_value(['port'])
+
+ # persistent-keepalive
+ if conf.exists(['persistent-keepalive']):
+ peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive'])
+
+ # preshared-key
+ if conf.exists(['preshared-key']):
+ peer['psk'] = conf.return_value(['preshared-key'])
+
+ # peer pubkeys
+ if conf.exists(['pubkey']):
+ key_eff = conf.return_effective_value(['pubkey'])
+ key_cfg = conf.return_value(['pubkey'])
+ peer['pubkey'] = key_cfg
+
+ # on a pubkey change we need to remove the pubkey first
+ # peers are identified by pubkey, so key update means
+ # peer removal and re-add
+ if key_eff != key_cfg and key_eff != None:
+ wg['peer_remove'].append(key_cfg)
+
+ # if a peer is disabled, we have to exec a remove for it's pubkey
+ if conf.exists(['disable']):
+ wg['peer_remove'].append(peer['pubkey'])
+ else:
+ wg['peer'].append(peer)
+
return wg
-def verify(c):
- if not c:
- return None
+def verify(wg):
+ interface = wg['intf']
- if c['delete']:
- interface = c['intfc']
+ if wg['deleted']:
is_member, bridge = is_bridge_member(interface)
if is_member:
# can not use a f'' formatted-string here as bridge would not get
@@ -189,98 +197,100 @@ def verify(c):
'is a member of bridge "{1}"!'.format(interface, bridge))
return None
- if not os.path.exists(c['pk']):
- raise ConfigError(
- "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'")
-
- if not c['delete']:
- if not c['addr']:
- raise ConfigError("ERROR: IP address required")
- if not c['peer']:
- raise ConfigError("ERROR: peer required")
- for p in c['peer']:
- if not c['peer'][p]['allowed-ips']:
- raise ConfigError("ERROR: allowed-ips required for peer " + p)
- if not c['peer'][p]['pubkey']:
- raise ConfigError("peer pubkey required for peer " + p)
-
-
-def apply(c):
- # no wg configs left, remove all interface from system
- # maybe move it into ifconfig.py
- if not c:
- net_devs = os.listdir('/sys/class/net/')
- for dev in net_devs:
- if os.path.isdir('/sys/class/net/' + dev):
- buf = open('/sys/class/net/' + dev + '/uevent', 'r').read()
- if re.search("DEVTYPE=wireguard", buf, re.I | re.M):
- wg_intf = re.sub("INTERFACE=", "", re.search(
- "INTERFACE=.*", buf, re.I | re.M).group(0))
- # XXX: we are ignoring any errors here
- run(f'ip l d dev {wg_intf} >/dev/null')
- return None
+ vrf_name = wg['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ if not os.path.exists(wg['pk']):
+ raise ConfigError('No keys found, generate them by executing:\n' \
+ '"run generate wireguard [keypair|named-keypairs]"')
+ if not wg['address']:
+ raise ConfigError(f'IP address required for interface "{interface}"!')
+
+ if not wg['peer']:
+ raise ConfigError(f'Peer required for interface "{interface}"!')
+
+ # run checks on individual configured WireGuard peer
+ for peer in wg['peer']:
+ peer_name = peer['name']
+ if not peer['allowed-ips']:
+ raise ConfigError(f'Peer allowed-ips required for peer "{peer_name}"!')
+
+ if not peer['pubkey']:
+ raise ConfigError(f'Peer public-key required for peer "{peer_name}"!')
+
+
+def apply(wg):
# init wg class
- intfc = WireGuardIf(c['intfc'])
+ w = WireGuardIf(wg['intf'])
# single interface removal
- if c['delete']:
- intfc.remove()
+ if wg['deleted']:
+ w.remove()
return None
- # remove IP addresses
- for ip in c['addr_remove']:
- intfc.del_addr(ip)
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in wg['address_remove']:
+ w.del_addr(addr)
+ for addr in wg['address']:
+ w.add_addr(addr)
- # add IP addresses
- for ip in c['addr']:
- intfc.add_addr(ip)
+ # Maximum Transmission Unit (MTU)
+ w.set_mtu(wg['mtu'])
- # interface mtu
- intfc.set_mtu(int(c['mtu']))
+ # update interface description used e.g. within SNMP
+ w.set_alias(wg['description'])
- # ifalias for snmp from description
- intfc.set_alias(str(c['descr']))
+ # assign/remove VRF
+ w.set_vrf(wg['vrf'])
# remove peers
- if c['peer_remove']:
- for pkey in c['peer_remove']:
- intfc.remove_peer(pkey)
+ for pub_key in wg['peer_remove']:
+ w.remove_peer(pub_key)
# peer pubkey
# setting up the wg interface
- intfc.config['private-key'] = c['pk']
- for p in c['peer']:
+ w.config['private-key'] = c['pk']
+
+ for peer in wg['peer']:
# peer pubkey
- intfc.config['pubkey'] = str(c['peer'][p]['pubkey'])
+ w.config['pubkey'] = peer['pubkey']
# peer allowed-ips
- intfc.config['allowed-ips'] = c['peer'][p]['allowed-ips']
+ w.config['allowed-ips'] = peer['allowed-ips']
# local listen port
- if c['lport']:
- intfc.config['port'] = c['lport']
+ if wg['lport']:
+ w.config['port'] = wg['lport']
# fwmark
if c['fwmark']:
- intfc.config['fwmark'] = c['fwmark']
+ w.config['fwmark'] = wg['fwmark']
+
# endpoint
- if c['peer'][p]['address'] and c['peer'][p]['port']:
- intfc.config['endpoint'] = "{}:{}".format(c['peer'][p]['address'], c['peer'][p]['port'])
+ if peer['address'] and peer['port']:
+ w.config['endpoint'] = '{}:{}'.format(
+ peer['address'], peer['port'])
# persistent-keepalive
- if 'persistent-keepalive' in c['peer'][p]:
- intfc.config['keepalive'] = c['peer'][p]['persistent-keepalive']
+ if peer['persistent_keepalive']:
+ w.config['keepalive'] = peer['persistent_keepalive']
# maybe move it into ifconfig.py
# preshared-key - needs to be read from a file
- if 'psk' in c['peer'][p]:
+ if peer['psk']:
psk_file = '/config/auth/wireguard/psk'
- old_umask = os.umask(0o077)
- open(psk_file, 'w').write(str(c['peer'][p]['psk']))
- os.umask(old_umask)
- intfc.config['psk'] = psk_file
- intfc.update()
+ with open(psk_file, 'w') as f:
+ f.write(peer['psk'])
+ w.config['psk'] = psk_file
+
+ w.update()
- # interface state
- intfc.set_admin_state(c['state'])
+ # Enable/Disable interface
+ if wg['disable']:
+ w.set_admin_state('down')
+ else:
+ w.set_admin_state('up')
return None
@@ -293,4 +303,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- sys.exit(1)
+ exit(1)
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 709085b0f..498c24df0 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -19,21 +19,18 @@ from sys import exit
from re import findall
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from netaddr import EUI, mac_unix_expanded
from vyos.config import Config
from vyos.configdict import list_diff, vlan_to_dict
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import WiFiIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
-from vyos.util import process_running, chmod_x, chown_file, run, is_bridge_member
+from vyos.util import chown, is_bridge_member, call
from vyos import ConfigError
+from vyos.template import render
-user = 'root'
-group = 'vyattacfg'
default_config_data = {
'address': [],
@@ -115,43 +112,16 @@ default_config_data = {
}
def get_conf_file(conf_type, intf):
- cfg_dir = '/var/run/' + conf_type
+ cfg_dir = '/run/' + conf_type
# create directory on demand
if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
+ os.makedirs(cfg_dir, 0o755)
+ chown(cfg_dir, 'root', 'vyattacfg')
- cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
+ cfg_file = cfg_dir + r'/{}.conf'.format(intf)
return cfg_file
-def get_pid(conf_type, intf):
- cfg_dir = '/var/run/' + conf_type
-
- # create directory on demand
- if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
-
- cfg_file = cfg_dir + r'/{}.pid'.format(intf)
- return cfg_file
-
-
-def get_wpa_suppl_config_name(intf):
- cfg_dir = '/var/run/wpa_supplicant'
-
- # create directory on demand
- if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
-
- cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
- return cfg_file
-
-
def get_config():
wifi = deepcopy(default_config_data)
conf = Config()
@@ -570,6 +540,9 @@ def verify(wifi):
if not wifi['phy']:
raise ConfigError('You must specify physical-device')
+ if not wifi['mode']:
+ raise ConfigError('You must specify a WiFi mode')
+
if wifi['op_mode'] == 'ap':
c = Config()
if not c.exists('system wifi-regulatory-domain'):
@@ -627,38 +600,20 @@ def verify(wifi):
return None
def generate(wifi):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "wifi")
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ interface = wifi['intf']
# always stop hostapd service first before reconfiguring it
- pidfile = get_pid('hostapd', wifi['intf'])
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- run(command)
-
+ call(f'systemctl stop hostapd@{interface}.service')
# always stop wpa_supplicant service first before reconfiguring it
- pidfile = get_pid('wpa_supplicant', wifi['intf'])
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- run(command)
+ call(f'systemctl stop wpa_supplicant@{interface}.service')
# Delete config files if interface is removed
if wifi['deleted']:
- if os.path.isfile(get_conf_file('hostapd', wifi['intf'])):
- os.unlink(get_conf_file('hostapd', wifi['intf']))
+ if os.path.isfile(get_conf_file('hostapd', interface)):
+ os.unlink(get_conf_file('hostapd', interface))
- if os.path.isfile(get_conf_file('wpa_supplicant', wifi['intf'])):
- os.unlink(get_conf_file('wpa_supplicant', wifi['intf']))
+ if os.path.isfile(get_conf_file('wpa_supplicant', interface)):
+ os.unlink(get_conf_file('wpa_supplicant', interface))
return None
@@ -676,7 +631,7 @@ def generate(wifi):
tmp |= 0x020000000000
# we now need to add an offset to our MAC address indicating this
# subinterfaces index
- tmp += int(findall(r'\d+', wifi['intf'])[0])
+ tmp += int(findall(r'\d+', interface)[0])
# convert integer to "real" MAC address representation
mac = EUI(hex(tmp).split('x')[-1])
@@ -686,22 +641,19 @@ def generate(wifi):
# render appropriate new config files depending on access-point or station mode
if wifi['op_mode'] == 'ap':
- tmpl = env.get_template('hostapd.conf.tmpl')
- config_text = tmpl.render(wifi)
- with open(get_conf_file('hostapd', wifi['intf']), 'w') as f:
- f.write(config_text)
+ conf = get_conf_file('hostapd', interface)
+ render(conf, 'wifi/hostapd.conf.tmpl', wifi)
elif wifi['op_mode'] == 'station':
- tmpl = env.get_template('wpa_supplicant.conf.tmpl')
- config_text = tmpl.render(wifi)
- with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f:
- f.write(config_text)
+ conf = get_conf_file('wpa_supplicant', interface)
+ render(conf, 'wifi/wpa_supplicant.conf.tmpl', wifi)
return None
def apply(wifi):
+ interface = wifi['intf']
if wifi['deleted']:
- w = WiFiIf(wifi['intf'])
+ w = WiFiIf(interface)
# delete interface
w.remove()
else:
@@ -714,7 +666,7 @@ def apply(wifi):
conf['phy'] = wifi['phy']
# Finally create the new interface
- w = WiFiIf(wifi['intf'], **conf)
+ w = WiFiIf(interface, **conf)
# assign/remove VRF
w.set_vrf(wifi['vrf'])
@@ -722,32 +674,20 @@ def apply(wifi):
# update interface description used e.g. within SNMP
w.set_alias(wifi['description'])
- # get DHCP config dictionary and update values
- opt = w.get_dhcp_options()
-
if wifi['dhcp_client_id']:
- opt['client_id'] = wifi['dhcp_client_id']
+ w.dhcp.v4.options['client_id'] = wifi['dhcp_client_id']
if wifi['dhcp_hostname']:
- opt['hostname'] = wifi['dhcp_hostname']
+ w.dhcp.v4.options['hostname'] = wifi['dhcp_hostname']
if wifi['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = wifi['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- w.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = w.get_dhcpv6_options()
+ w.dhcp.v4.options['vendor_class_id'] = wifi['dhcp_vendor_class_id']
if wifi['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ w.dhcp.v6.options['dhcpv6_prm_only'] = True
if wifi['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- w.set_dhcpv6_options(opt)
+ w.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
w.set_link_detect(wifi['disable_link_detect'])
@@ -786,7 +726,7 @@ def apply(wifi):
# remove no longer required VLAN interfaces (vif)
for vif in wifi['vif_remove']:
- e.del_vlan(vif)
+ w.del_vlan(vif)
# create VLAN interfaces (vif)
for vif in wifi['vif']:
@@ -796,11 +736,11 @@ def apply(wifi):
try:
# on system bootup the above condition is true but the interface
# does not exists, which throws an exception, but that's legal
- e.del_vlan(vif['id'])
+ w.del_vlan(vif['id'])
except:
pass
- vlan = e.add_vlan(vif['id'])
+ vlan = w.add_vlan(vif['id'])
apply_vlan_config(vlan, vif)
# Enable/Disable interface - interface is always placed in
@@ -811,38 +751,10 @@ def apply(wifi):
# Physical interface is now configured. Proceed by starting hostapd or
# wpa_supplicant daemon. When type is monitor we can just skip this.
if wifi['op_mode'] == 'ap':
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + get_pid('hostapd', wifi['intf'])
- command += ' --exec /usr/sbin/hostapd'
- # now pass arguments to hostapd binary
- command += ' -- '
- command += ' -B'
- command += ' -P ' + get_pid('hostapd', wifi['intf'])
- command += ' ' + get_conf_file('hostapd', wifi['intf'])
-
- # execute assembled command
- run(command)
+ call(f'systemctl start hostapd@{interface}.service')
elif wifi['op_mode'] == 'station':
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + get_pid('hostapd', wifi['intf'])
- command += ' --exec /sbin/wpa_supplicant'
- # now pass arguments to hostapd binary
- command += ' -- '
- command += ' -s -B -D nl80211'
- command += ' -P ' + get_pid('wpa_supplicant', wifi['intf'])
- command += ' -i ' + wifi['intf']
- command += ' -c ' + \
- get_conf_file('wpa_supplicant', wifi['intf'])
-
- # execute assembled command
- run(command)
+ call(f'systemctl start wpa_supplicant@{interface}.service')
return None
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
index 49445aaa4..da1855cd9 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -18,13 +18,17 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import chown_file, chmod_x, cmd, run, is_bridge_member
+from vyos.util import chown
+from vyos.util import chmod_755
+from vyos.util import is_bridge_member
+from vyos.util import cmd
+from vyos.util import call
from vyos import ConfigError
+from vyos.template import render
+
default_config_data = {
'address': [],
@@ -48,7 +52,7 @@ def check_kmod():
modules = ['option', 'usb_wwan', 'usbserial']
for module in modules:
if not os.path.exists(f'/sys/module/{module}'):
- if run(f'modprobe {module}') != 0:
+ if call(f'modprobe {module}') != 0:
raise ConfigError(f'Loading Kernel module {module} failed')
def get_config():
@@ -139,11 +143,6 @@ def verify(wwan):
return None
def generate(wwan):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wwan')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# set up configuration file path variables where our templates will be
# rendered into
intf = wwan['intf']
@@ -173,39 +172,20 @@ def generate(wwan):
else:
# Create PPP configuration files
- tmpl = env.get_template('peer.tmpl')
- config_text = tmpl.render(wwan)
- with open(config_wwan, 'w') as f:
- f.write(config_text)
-
+ render(config_wwan, 'wwan/peer.tmpl', wwan)
# Create PPP chat script
- tmpl = env.get_template('chat.tmpl')
- config_text = tmpl.render(wwan)
- with open(config_wwan_chat, 'w') as f:
- f.write(config_text)
-
+ render(config_wwan_chat, 'wwan/chat.tmpl', wwan)
# Create script for ip-pre-up.d
- tmpl = env.get_template('ip-pre-up.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_pre_up, 'w') as f:
- f.write(config_text)
-
+ render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', wwan)
# Create script for ip-up.d
- tmpl = env.get_template('ip-up.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_ip_up, 'w') as f:
- f.write(config_text)
-
+ render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', wwan)
# Create script for ip-down.d
- tmpl = env.get_template('ip-down.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_ip_down, 'w') as f:
- f.write(config_text)
+ render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', wwan)
# make generated script file executable
- chmod_x(script_wwan_pre_up)
- chmod_x(script_wwan_ip_up)
- chmod_x(script_wwan_ip_down)
+ chmod_755(script_wwan_pre_up)
+ chmod_755(script_wwan_ip_up)
+ chmod_755(script_wwan_ip_down)
return None
@@ -219,7 +199,7 @@ def apply(wwan):
intf = wwan['intf']
cmd(f'systemctl start ppp@{intf}.service')
# make logfile owned by root / vyattacfg
- chown_file(wwan['logfile'], 'root', 'vyattacfg')
+ chown(wwan['logfile'], 'root', 'vyattacfg')
return None
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index c2f5c8e07..4fffa11ee 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -18,13 +18,13 @@ import re
import os
from time import sleep
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
+
ra_conn_name = "remote-access"
charon_conf_file = "/etc/strongswan.d/charon.conf"
@@ -99,7 +99,7 @@ def get_config():
### Remove config from file by delimiter
def remove_confs(delim_begin, delim_end, conf_file):
- run("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file)
+ call("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file)
### Checking certificate storage and notice if certificate not in /config directory
@@ -112,7 +112,7 @@ def check_cert_file_store(cert_name, file_path, dts_path):
else:
### Cpy file to /etc/ipsec.d/certs/ /etc/ipsec.d/cacerts/
# todo make check
- ret = run('cp -f '+file_path+' '+dts_path)
+ ret = call('cp -f '+file_path+' '+dts_path)
if ret:
raise ConfigError("L2TP VPN configuration error: Cannot copy "+file_path)
@@ -147,43 +147,26 @@ def verify(data):
raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.")
def generate(data):
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipsec')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('charon.tmpl')
- config_text = tmpl.render(data)
- with open(charon_conf_file, 'w') as f:
- f.write(config_text)
+ render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True)
if data["ipsec_l2tp"]:
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
- tmpl = env.get_template('ipsec.secrets.tmpl')
- l2pt_ipsec_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(ipsec_secrets_flie,'w') as f:
- f.write(l2pt_ipsec_secrets_txt)
+ render(ipsec_secrets_flie, 'ipsec/ipsec.secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
- tmpl = env.get_template('remote-access.tmpl')
- ipsec_ra_conn_txt = tmpl.render(c)
old_umask = os.umask(0o077)
# Create tunnels directory if does not exist
if not os.path.exists(ipsec_ra_conn_dir):
os.makedirs(ipsec_ra_conn_dir)
- with open(ipsec_ra_conn_file,'w') as f:
- f.write(ipsec_ra_conn_txt)
+ render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', c, trim_blocks=True)
os.umask(old_umask)
-
- tmpl = env.get_template('ipsec.conf.tmpl')
- l2pt_ipsec_conf_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(ipsec_conf_flie,'a') as f:
- f.write(l2pt_ipsec_conf_txt)
+ render(ipsec_conf_flie, 'ipsec/ipsec.conf.tmpl', c, trim_blocks=True)
os.umask(old_umask)
else:
@@ -193,12 +176,12 @@ def generate(data):
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
def restart_ipsec():
- run('ipsec restart >&/dev/null')
+ call('ipsec restart >&/dev/null')
# counter for apply swanctl config
counter = 10
while counter <= 10:
if os.path.exists(charon_pidfile):
- run('swanctl -q >&/dev/null')
+ call('swanctl -q >&/dev/null')
break
counter -=1
sleep(1)
diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py
index a4dbecbaa..2db31d3fc 100755
--- a/src/conf_mode/le_cert.py
+++ b/src/conf_mode/le_cert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-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
@@ -13,8 +13,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#
import sys
import os
@@ -22,8 +20,8 @@ import os
import vyos.defaults
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
-
+from vyos.util import cmd
+from vyos.util import call
vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode']
@@ -85,17 +83,17 @@ def generate(cert):
# certbot will attempt to reload nginx, even with 'certonly';
# start nginx if not active
- ret = run('systemctl is-active --quiet nginx.ervice')
+ ret = call('systemctl is-active --quiet nginx.service')
if ret:
- run('sudo systemctl start nginx.service')
+ call('systemctl start nginx.service')
request_certbot(cert)
def apply(cert):
if cert is not None:
- run('sudo systemctl restart certbot.timer')
+ call('systemctl restart certbot.timer')
else:
- run('sudo systemctl stop certbot.timer')
+ call('systemctl stop certbot.timer')
return None
for dep in dependencies:
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index c090bba83..d128c1fe6 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -18,15 +18,14 @@ import os
import re
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
from vyos.validate import is_addr_assigned,is_loopback_addr
-from vyos.defaults import directories as vyos_data_dir
from vyos.version import get_version_data
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = "/etc/default/lldpd"
@@ -210,11 +209,6 @@ def generate(lldp):
if lldp is None:
return
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'lldp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# generate listen on interfaces
for intf in lldp['interface_list']:
tmp = ''
@@ -226,25 +220,18 @@ def generate(lldp):
lldp['options']['listen_on'].append(tmp)
# generate /etc/default/lldpd
- tmpl = env.get_template('lldpd.tmpl')
- config_text = tmpl.render(lldp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'lldp/lldpd.tmpl', lldp)
# generate /etc/lldpd.d/01-vyos.conf
- tmpl = env.get_template('vyos.conf.tmpl')
- config_text = tmpl.render(lldp)
- with open(vyos_config_file, 'w') as f:
- f.write(config_text)
+ render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp)
def apply(lldp):
if lldp:
# start/restart lldp service
- run('sudo systemctl restart lldpd.service')
+ call('sudo systemctl restart lldpd.service')
else:
# LLDP service has been terminated
- run('sudo systemctl stop lldpd.service')
+ call('sudo systemctl stop lldpd.service')
os.unlink(config_file)
os.unlink(vyos_config_file)
diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py
index 2bccd9153..a652553f7 100755
--- a/src/conf_mode/mdns_repeater.py
+++ b/src/conf_mode/mdns_repeater.py
@@ -18,14 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import ifaddresses, AF_INET
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
-
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/mdns-repeater'
@@ -82,25 +80,16 @@ def generate(mdns):
print('Warning: mDNS repeater will be deactivated because it is disabled')
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mdns-repeater')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('mdns-repeater.tmpl')
- config_text = tmpl.render(mdns)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns)
return None
def apply(mdns):
if (mdns is None) or mdns['disabled']:
- run('sudo systemctl stop mdns-repeater')
+ call('systemctl stop mdns-repeater.service')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- run('sudo systemctl restart mdns-repeater')
+ call('systemctl restart mdns-repeater.service')
return None
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 998022a8c..6d32f7fd6 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -18,14 +18,12 @@ import os
from copy import deepcopy
from ipaddress import ip_network
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.util import call
+from vyos.template import render
from vyos import ConfigError
-from vyos.util import run
-
config_file = r'/etc/ntp.conf'
@@ -100,24 +98,15 @@ def generate(ntp):
if ntp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ntp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('ntp.conf.tmpl')
- config_text = tmpl.render(ntp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'ntp/ntp.conf.tmpl', ntp)
return None
def apply(ntp):
if ntp is not None:
- run('sudo systemctl restart ntp.service')
+ call('sudo systemctl restart ntp.service')
else:
# NTP support is removed in the commit
- run('sudo systemctl stop ntp.service')
+ call('sudo systemctl stop ntp.service')
os.unlink(config_file)
return None
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index a62d2158e..ed8c3637b 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -18,13 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv6_link_local, is_ipv6
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/bfd.frr'
@@ -191,23 +190,14 @@ def generate(bfd):
if bfd is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'frr-bfd')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('bfd.frr.tmpl')
- config_text = tmpl.render(bfd)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'frr-bfd/bfd.frr.tmpl', bfd)
return None
def apply(bfd):
if bfd is None:
return None
- run("vtysh -d bfdd -f " + config_file)
+ call("vtysh -d bfdd -f " + config_file)
if os.path.exists(config_file):
os.remove(config_file)
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index 6e819a15a..9b338c5b9 100755
--- a/src/conf_mode/protocols_igmp.py
+++ b/src/conf_mode/protocols_igmp.py
@@ -17,13 +17,12 @@
import os
from ipaddress import IPv4Address
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos import ConfigError
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/igmp.frr'
@@ -88,16 +87,7 @@ def generate(igmp):
if igmp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('igmp.frr.tmpl')
- config_text = tmpl.render(igmp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'igmp/igmp.frr.tmpl', igmp)
return None
def apply(igmp):
@@ -105,7 +95,7 @@ def apply(igmp):
return None
if os.path.exists(config_file):
- run("sudo vtysh -d pimd -f " + config_file)
+ call("sudo vtysh -d pimd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 6e5d08397..0a241277d 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -16,18 +16,16 @@
import os
-from jinja2 import FileSystemLoader, Environment
-
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/ldpd.frr'
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
conf = Config()
@@ -129,16 +127,7 @@ def generate(mpls):
if mpls is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mpls')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('ldpd.frr.tmpl')
- config_text = tmpl.render(mpls)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'mpls/ldpd.frr.tmpl', mpls)
return None
def apply(mpls):
@@ -162,7 +151,7 @@ def apply(mpls):
operate_mpls_on_intfc(diactive_ifaces, 0)
if os.path.exists(config_file):
- run("sudo vtysh -d ldpd -f " + config_file)
+ call("sudo vtysh -d ldpd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 9b74fe992..f12de4a72 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -17,13 +17,12 @@
import os
from ipaddress import IPv4Address
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/pimd.frr'
@@ -115,16 +114,7 @@ def generate(pim):
if pim is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pim')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('pimd.frr.tmpl')
- config_text = tmpl.render(pim)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'pim/pimd.frr.tmpl', pim)
return None
def apply(pim):
@@ -132,7 +122,7 @@ def apply(pim):
return None
if os.path.exists(config_file):
- run("vtysh -d pimd -f " + config_file)
+ call("vtysh -d pimd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index bd1d44bc8..236480854 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -17,16 +17,15 @@
import os
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from pwd import getpwnam
from socket import gethostname
from sys import exit
from urllib3 import PoolManager
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/salt/minion'
@@ -88,18 +87,10 @@ def generate(salt):
if salt is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'salt-minion')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
if not os.path.exists(directory):
os.makedirs(directory)
- tmpl = env.get_template('minion.tmpl')
- config_text = tmpl.render(salt)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'salt-minion/minion.tmpl', salt)
path = "/etc/salt/"
for path in paths:
@@ -126,10 +117,10 @@ def generate(salt):
def apply(salt):
if salt is not None:
- run("sudo systemctl restart salt-minion")
+ call("sudo systemctl restart salt-minion")
else:
# Salt access is removed in the commit
- run("sudo systemctl stop salt-minion")
+ call("sudo systemctl stop salt-minion")
os.unlink(config_file)
return None
diff --git a/src/conf_mode/service-ipoe.py b/src/conf_mode/service-ipoe.py
index 5bd4aea2e..3a14d92ef 100755
--- a/src/conf_mode/service-ipoe.py
+++ b/src/conf_mode/service-ipoe.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
ipoe_cnf_dir = r'/etc/accel-ppp/ipoe'
ipoe_cnf = ipoe_cnf_dir + r'/ipoe.config'
@@ -219,25 +219,15 @@ def generate(c):
if c == None or not c:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipoe-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
c['thread_cnt'] = _get_cpu()
if c['auth']['mech'] == 'local':
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'ipoe-server/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
- tmpl = env.get_template('ipoe.config.tmpl')
- config_text = tmpl.render(c)
- with open(ipoe_cnf, 'w') as f:
- f.write(config_text)
+ render(ipoe_cnf, 'ipoe-server/ipoe.config.tmpl', c, trim_blocks=True)
+ # return c ??
return c
diff --git a/src/conf_mode/service-pppoe.py b/src/conf_mode/service-pppoe.py
index d3fc82406..a96249199 100755
--- a/src/conf_mode/service-pppoe.py
+++ b/src/conf_mode/service-pppoe.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
pidfile = r'/var/run/accel_pppoe.pid'
pppoe_cnf_dir = r'/etc/accel-ppp/pppoe'
@@ -376,11 +376,6 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pppoe-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
# accel-cmd reload doesn't work so any change results in a restart of the
# daemon
try:
@@ -394,17 +389,11 @@ def generate(c):
else:
c['thread_cnt'] = int(os.cpu_count() / 2)
- tmpl = env.get_template('pppoe.config.tmpl')
- config_text = tmpl.render(c)
- with open(pppoe_conf, 'w') as f:
- f.write(config_text)
+ render(pppoe_conf, 'pppoe-server/pppoe.config.tmpl', c, trim_blocks=True)
if c['authentication']['local-users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'pppoe-server/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
return c
diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py
index 0173b7242..620f3eacf 100755
--- a/src/conf_mode/service-router-advert.py
+++ b/src/conf_mode/service-router-advert.py
@@ -16,14 +16,13 @@
import os
-from jinja2 import FileSystemLoader, Environment
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/radvd.conf'
@@ -139,15 +138,7 @@ def generate(rtradv):
if not rtradv['interfaces']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'router-advert')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('radvd.conf.tmpl')
- config_text = tmpl.render(rtradv)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True)
# adjust file permissions of new configuration file
if os.path.exists(config_file):
@@ -158,13 +149,13 @@ def generate(rtradv):
def apply(rtradv):
if not rtradv['interfaces']:
# bail out early - looks like removal from running config
- run('systemctl stop radvd.service')
+ call('systemctl stop radvd.service')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- run('systemctl restart radvd.service')
+ call('systemctl restart radvd.service')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 414236c88..d654dcb84 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -20,14 +20,13 @@ from binascii import hexlify
from time import sleep
from stat import S_IRWXU, S_IXGRP, S_IXOTH, S_IROTH, S_IRGRP
from sys import exit
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv4, is_addr_assigned
from vyos.version import get_version_data
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file_client = r'/etc/snmp/snmp.conf'
@@ -509,7 +508,7 @@ def generate(snmp):
#
# As we are manipulating the snmpd user database we have to stop it first!
# This is even save if service is going to be removed
- run('systemctl stop snmpd.service')
+ call('systemctl stop snmpd.service')
config_files = [config_file_client, config_file_daemon, config_file_access,
config_file_user]
for file in config_files:
@@ -518,34 +517,14 @@ def generate(snmp):
if snmp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'snmp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# Write client config file
- tmpl = env.get_template('etc.snmp.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_client, 'w') as f:
- f.write(config_text)
-
+ render(config_file_client, 'snmp/etc.snmp.conf.tmpl', snmp)
# Write server config file
- tmpl = env.get_template('etc.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_daemon, 'w') as f:
- f.write(config_text)
-
+ render(config_file_daemon, 'snmp/etc.snmpd.conf.tmpl', snmp)
# Write access rights config file
- tmpl = env.get_template('usr.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_access, 'w') as f:
- f.write(config_text)
-
+ render(config_file_access, 'snmp/usr.snmpd.conf.tmpl', snmp)
# Write access rights config file
- tmpl = env.get_template('var.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_user, 'w') as f:
- f.write(config_text)
+ render(config_file_user, 'snmp/var.snmpd.conf.tmpl', snmp)
return None
@@ -554,7 +533,7 @@ def apply(snmp):
return None
# start SNMP daemon
- run("systemctl restart snmpd.service")
+ call("systemctl restart snmpd.service")
# Passwords are not available immediately in the configuration file,
# after daemon startup - we wait until they have been processed by
@@ -595,15 +574,15 @@ def apply(snmp):
# Now update the running configuration
#
- # Currently when executing run() the environment does not
+ # Currently when executing call() the environment does not
# have the vyos_libexec_dir variable set, see Phabricator T685.
- run('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['auth_pw']))
- run('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['priv_pw']))
- run('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
- run('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['auth_pw']))
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['priv_pw']))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
# Enable AgentX in FRR
- run('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+ call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
return None
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index a85dcd7f2..ae79eac2d 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -15,13 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/ssh/sshd_config'
@@ -120,23 +119,15 @@ def generate(ssh):
if ssh is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ssh')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('sshd_config.tmpl')
- config_text = tmpl.render(ssh)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True)
return None
def apply(ssh):
if ssh is not None and 'port' in ssh.keys():
- run("sudo systemctl restart ssh.service")
+ call("sudo systemctl restart ssh.service")
else:
# SSH access is removed in the commit
- run("sudo systemctl stop ssh.service")
+ call("sudo systemctl stop ssh.service")
if os.path.isfile(config_file):
os.unlink(config_file)
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index 66f563939..8a1ac8411 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -20,7 +20,7 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -31,7 +31,7 @@ default_config_data = {
}
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
ip_opt = deepcopy(default_config_data)
diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py
index 4e3de6fe9..04a063564 100755
--- a/src/conf_mode/system-ipv6.py
+++ b/src/conf_mode/system-ipv6.py
@@ -21,7 +21,7 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
ipv6_disable_file = '/etc/modprobe.d/vyos_disable_ipv6.conf'
@@ -37,7 +37,7 @@ default_config_data = {
}
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
ip_opt = deepcopy(default_config_data)
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 7c99fce39..6008ca0b3 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -16,7 +16,6 @@
import os
-from jinja2 import FileSystemLoader, Environment
from psutil import users
from pwd import getpwall, getpwnam
from stat import S_IRUSR, S_IWUSR, S_IRWXU, S_IRGRP, S_IXGRP
@@ -24,9 +23,12 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.template import render
+
radius_config_file = "/etc/pam_radius_auth.conf"
@@ -207,19 +209,19 @@ def generate(login):
# remove old plaintext password
# and set new encrypted password
- run("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name']))
- run("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted']))
+ os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name']))
+ os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted']))
- if len(login['radius_server']) > 0:
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'system-login')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ # env = os.environ.copy()
+ # env['vyos_libexec_dir'] = '/usr/libexec/vyos'
- tmpl = env.get_template('pam_radius_auth.conf.tmpl')
- config_text = tmpl.render(login)
- with open(radius_config_file, 'w') as f:
- f.write(config_text)
+ # call("/opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password ''".format(user['name']),
+ # env=env)
+ # call("/opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}'".format(user['name'], user['password_encrypted']),
+ # env=env)
+
+ if len(login['radius_server']) > 0:
+ render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', login)
uid = getpwnam('root').pw_uid
gid = getpwnam('root').pw_gid
@@ -255,7 +257,7 @@ def apply(login):
command += " {}".format(user['name'])
try:
- run(command)
+ cmd(command)
uid = getpwnam(user['name']).pw_uid
gid = getpwnam(user['name']).pw_gid
@@ -295,10 +297,10 @@ def apply(login):
# Logout user if he is logged in
if user in list(set([tmp[0] for tmp in users()])):
print('{} is logged in, forcing logout'.format(user))
- run('pkill -HUP -u {}'.format(user))
+ call('pkill -HUP -u {}'.format(user))
# Remove user account but leave home directory to be safe
- run('userdel -r {} 2>/dev/null'.format(user))
+ call(f'userdel -r {user}', stderr=DEVNULL)
except Exception as e:
raise ConfigError('Deleting user "{}" raised an exception: {}'.format(user, e))
@@ -308,8 +310,10 @@ def apply(login):
#
if len(login['radius_server']) > 0:
try:
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
# Enable RADIUS in PAM
- run("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius")
+ cmd("pam-auth-update --package --enable radius", env=env)
# Make NSS system aware of RADIUS, too
command = "sed -i -e \'/\smapname/b\' \
@@ -320,15 +324,18 @@ def apply(login):
-e \'/^group:[^#]*$/s/: */&mapname /\' \
/etc/nsswitch.conf"
- run(command)
+ cmd(command)
except Exception as e:
raise ConfigError('RADIUS configuration failed: {}'.format(e))
else:
try:
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
+
# Disable RADIUS in PAM
- run("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius")
+ cmd("pam-auth-update --package --remove radius", env=env)
command = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \
-e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \
@@ -336,10 +343,10 @@ def apply(login):
-e \'s/[ \t]*$//\' \
/etc/nsswitch.conf"
- run(command)
+ cmd(command)
except Exception as e:
- raise ConfigError('Removing RADIUS configuration failed'.format(e))
+ raise ConfigError('Removing RADIUS configuration failed.\n{}'.format(e))
return None
diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py
index 063a82463..b3dbc82fb 100755
--- a/src/conf_mode/system-options.py
+++ b/src/conf_mode/system-options.py
@@ -52,9 +52,9 @@ def generate(opt):
def apply(opt):
# Beep action
if opt['beep_if_fully_booted']:
- run('systemctl enable vyos-beep.service >/dev/null 2>&1')
+ run('systemctl enable vyos-beep.service')
else:
- run('systemctl disable vyos-beep.service >/dev/null 2>&1')
+ run('systemctl disable vyos-beep.service')
# Ctrl-Alt-Delete action
if opt['ctrl_alt_del'] == 'ignore':
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 25b9b5bed..9da3d9157 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -17,13 +17,13 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
def get_config():
c = Config()
@@ -192,22 +192,13 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'syslog')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('rsyslog.conf.tmpl')
- config_text = tmpl.render(c)
- with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f:
- f.write(config_text)
+ conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
+ render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True)
# eventually write for each file its own logrotate file, since size is
# defined it shouldn't matter
- tmpl = env.get_template('logrotate.tmpl')
- config_text = tmpl.render(c)
- with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
- f.write(config_text)
+ conf = '/etc/logrotate.d/vyos-rsyslog'
+ render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True)
def verify(c):
@@ -253,8 +244,8 @@ def verify(c):
def apply(c):
if not c:
- return run('systemctl stop syslog')
- return run('systemctl restart syslog')
+ return run('systemctl stop syslog.service')
+ return run('systemctl restart syslog.service')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py
index 2f8dc9e89..25b949a79 100755
--- a/src/conf_mode/system-timezone.py
+++ b/src/conf_mode/system-timezone.py
@@ -20,7 +20,7 @@ import os
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -42,7 +42,7 @@ def generate(tz):
pass
def apply(tz):
- run('/usr/bin/timedatectl set-timezone {}'.format(tz['name']))
+ call('/usr/bin/timedatectl set-timezone {}'.format(tz['name']))
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py
index 943c42274..b222df0a9 100755
--- a/src/conf_mode/system-wifi-regdom.py
+++ b/src/conf_mode/system-wifi-regdom.py
@@ -18,11 +18,11 @@ import os
from copy import deepcopy
from sys import exit
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
+from vyos.template import render
+
config_80211_file='/etc/modprobe.d/cfg80211.conf'
config_crda_file='/etc/default/crda'
@@ -67,21 +67,8 @@ def generate(regdom):
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wifi')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('cfg80211.conf.tmpl')
- config_text = tmpl.render(regdom)
- with open(config_80211_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('crda.tmpl')
- config_text = tmpl.render(regdom)
- with open(config_crda_file, 'w') as f:
- f.write(config_text)
-
+ render(config_80211_file, 'wifi/cfg80211.conf.tmpl', regdom)
+ render(config_crda_file, 'wifi/crda.tmpl', regdom)
return None
def apply(regdom):
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index df8155084..94c8bcf03 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -20,14 +20,13 @@ import pwd
from copy import deepcopy
from glob import glob
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv4, is_addr_assigned
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/tftpd'
@@ -90,11 +89,6 @@ def generate(tftpd):
if tftpd is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'tftp-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
idx = 0
for listen in tftpd['listen']:
config = deepcopy(tftpd)
@@ -103,11 +97,8 @@ def generate(tftpd):
else:
config['listen'] = ["[" + listen + "]" + tftpd['port'] + " -6"]
- tmpl = env.get_template('default.tmpl')
- config_text = tmpl.render(config)
file = config_file + str(idx)
- with open(file, 'w') as f:
- f.write(config_text)
+ render(file, 'tftp-server/default.tmpl', config)
idx = idx + 1
@@ -115,7 +106,7 @@ def generate(tftpd):
def apply(tftpd):
# stop all services first - then we will decide
- run('systemctl stop tftpd@{0..20}')
+ call('systemctl stop tftpd@{0..20}.service')
# bail out early - e.g. service deletion
if tftpd is None:
@@ -140,7 +131,7 @@ def apply(tftpd):
idx = 0
for listen in tftpd['listen']:
- run('systemctl restart tftpd@{0}.service'.format(idx))
+ call('systemctl restart tftpd@{0}.service'.format(idx))
idx = idx + 1
return None
diff --git a/src/conf_mode/vpn-pptp.py b/src/conf_mode/vpn-pptp.py
index 45b2c4b40..15b80f984 100755
--- a/src/conf_mode/vpn-pptp.py
+++ b/src/conf_mode/vpn-pptp.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
pidfile = r'/var/run/accel_pptp.pid'
pptp_cnf_dir = r'/etc/accel-ppp/pptp'
@@ -206,11 +206,6 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pptp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
# accel-cmd reload doesn't work so any change results in a restart of the daemon
try:
if os.cpu_count() == 1:
@@ -223,19 +218,13 @@ def generate(c):
else:
c['thread_cnt'] = int(os.cpu_count()/2)
- tmpl = env.get_template('pptp.config.tmpl')
- config_text = tmpl.render(c)
- with open(pptp_conf, 'w') as f:
- f.write(config_text)
+ render(pptp_conf, 'pptp/pptp.config.tmpl', c, trim_blocks=True)
if c['authentication']['local-users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'pptp/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
-
+ # return c ??
return c
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
new file mode 100755
index 000000000..a8b183bef
--- /dev/null
+++ b/src/conf_mode/vpn_l2tp.py
@@ -0,0 +1,386 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-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 <http://www.gnu.org/licenses/>.
+
+import os
+import re
+
+from copy import deepcopy
+from stat import S_IRUSR, S_IWUSR, S_IRGRP
+from sys import exit
+from time import sleep
+
+from ipaddress import ip_network
+
+from vyos.config import Config
+from vyos.util import call
+from vyos.validate import is_ipv4
+from vyos import ConfigError
+from vyos.template import render
+
+
+l2tp_conf = '/run/accel-pppd/l2tp.conf'
+l2tp_chap_secrets = '/run/accel-pppd/l2tp.chap-secrets'
+
+default_config_data = {
+ 'auth_mode': 'local',
+ 'auth_ppp_mppe': 'prefer',
+ 'auth_proto': ['auth_mschap_v2'],
+ 'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template
+ 'client_ip_pool': None,
+ 'client_ip_subnets': [],
+ 'client_ipv6_pool': [],
+ 'client_ipv6_delegate_prefix': [],
+ 'dnsv4': [],
+ 'dnsv6': [],
+ 'gateway_address': '10.255.255.0',
+ 'local_users' : [],
+ 'mtu': '1436',
+ 'outside_addr': '',
+ 'ppp_mppe': 'prefer',
+ 'ppp_echo_failure' : '3',
+ 'ppp_echo_interval' : '30',
+ 'ppp_echo_timeout': '0',
+ 'radius_server': [],
+ 'radius_acct_tmo': '3',
+ 'radius_max_try': '3',
+ 'radius_timeout': '3',
+ 'radius_nas_id': '',
+ 'radius_nas_ip': '',
+ 'radius_source_address': '',
+ 'radius_shaper_attr': '',
+ 'radius_shaper_vendor': '',
+ 'radius_dynamic_author': '',
+ 'wins': [],
+ 'ip6_column': [],
+ 'thread_cnt': 1
+}
+
+def get_config():
+ conf = Config()
+ base_path = ['vpn', 'l2tp', 'remote-access']
+ if not conf.exists(base_path):
+ return None
+
+ conf.set_level(base_path)
+ l2tp = deepcopy(default_config_data)
+
+ cpu = os.cpu_count()
+ if cpu > 1:
+ l2tp['thread_cnt'] = int(cpu/2)
+
+ ### general options ###
+ if conf.exists(['name-server']):
+ for name_server in conf.return_values(['name-server']):
+ if is_ipv4(name_server):
+ l2tp['dnsv4'].append(name_server)
+ else:
+ l2tp['dnsv6'].append(name_server)
+
+ if conf.exists(['wins-server']):
+ l2tp['wins'] = conf.return_values(['wins-server'])
+
+ if conf.exists('outside-address'):
+ l2tp['outside_addr'] = conf.return_value('outside-address')
+
+ if conf.exists(['authentication', 'mode']):
+ l2tp['auth_mode'] = conf.return_value(['authentication', 'mode'])
+
+ if conf.exists(['authentication', 'protocols']):
+ auth_mods = {
+ 'pap': 'auth_pap',
+ 'chap': 'auth_chap_md5',
+ 'mschap': 'auth_mschap_v1',
+ 'mschap-v2': 'auth_mschap_v2'
+ }
+
+ for proto in conf.return_values(['authentication', 'protocols']):
+ l2tp['auth_proto'].append(auth_mods[proto])
+
+ if conf.exists(['authentication', 'mppe']):
+ l2tp['auth_ppp_mppe'] = conf.return_value(['authentication', 'mppe'])
+
+ #
+ # local auth
+ if conf.exists(['authentication', 'local-users']):
+ for username in conf.list_nodes(['authentication', 'local-users', 'username']):
+ user = {
+ 'name' : username,
+ 'password' : '',
+ 'state' : 'enabled',
+ 'ip' : '*',
+ 'upload' : None,
+ 'download' : None
+ }
+
+ conf.set_level(base_path + ['authentication', 'local-users', 'username', username])
+
+ if conf.exists(['password']):
+ user['password'] = conf.return_value(['password'])
+
+ if conf.exists(['disable']):
+ user['state'] = 'disable'
+
+ if conf.exists(['static-ip']):
+ user['ip'] = conf.return_value(['static-ip'])
+
+ if conf.exists(['rate-limit', 'download']):
+ user['download'] = conf.return_value(['rate-limit', 'download'])
+
+ if conf.exists(['rate-limit', 'upload']):
+ user['upload'] = conf.return_value(['rate-limit', 'upload'])
+
+ l2tp['local_users'].append(user)
+
+ #
+ # RADIUS auth and settings
+ conf.set_level(base_path + ['authentication', 'radius'])
+ if conf.exists(['server']):
+ for server in conf.list_nodes(['server']):
+ radius = {
+ 'server' : server,
+ 'key' : '',
+ 'fail_time' : 0,
+ 'port' : '1812'
+ }
+
+ conf.set_level(base_path + ['authentication', 'radius', 'server', server])
+
+ if conf.exists(['fail-time']):
+ radius['fail-time'] = conf.return_value(['fail-time'])
+
+ if conf.exists(['port']):
+ radius['port'] = conf.return_value(['port'])
+
+ if conf.exists(['key']):
+ radius['key'] = conf.return_value(['key'])
+
+ if not conf.exists(['disable']):
+ l2tp['radius_server'].append(radius)
+
+ #
+ # advanced radius-setting
+ conf.set_level(base_path + ['authentication', 'radius'])
+
+ if conf.exists(['acct-timeout']):
+ l2tp['radius_acct_tmo'] = conf.return_value(['acct-timeout'])
+
+ if conf.exists(['max-try']):
+ l2tp['radius_max_try'] = conf.return_value(['max-try'])
+
+ if conf.exists(['timeout']):
+ l2tp['radius_timeout'] = conf.return_value(['timeout'])
+
+ if conf.exists(['nas-identifier']):
+ l2tp['radius_nas_id'] = conf.return_value(['nas-identifier'])
+
+ if conf.exists(['nas-ip-address']):
+ l2tp['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
+
+ if conf.exists(['source-address']):
+ l2tp['radius_source_address'] = conf.return_value(['source-address'])
+
+ # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA)
+ if conf.exists(['dynamic-author']):
+ dae = {
+ 'port' : '',
+ 'server' : '',
+ 'key' : ''
+ }
+
+ if conf.exists(['dynamic-author', 'server']):
+ dae['server'] = conf.return_value(['dynamic-author', 'server'])
+
+ if conf.exists(['dynamic-author', 'port']):
+ dae['port'] = conf.return_value(['dynamic-author', 'port'])
+
+ if conf.exists(['dynamic-author', 'key']):
+ dae['key'] = conf.return_value(['dynamic-author', 'key'])
+
+ l2tp['radius_dynamic_author'] = dae
+
+ if conf.exists(['rate-limit', 'enable']):
+ l2tp['radius_shaper_attr'] = 'Filter-Id'
+ c_attr = ['rate-limit', 'enable', 'attribute']
+ if conf.exists(c_attr):
+ l2tp['radius_shaper_attr'] = conf.return_value(c_attr)
+
+ c_vendor = ['rate-limit', 'enable', 'vendor']
+ if conf.exists(c_vendor):
+ l2tp['radius_shaper_vendor'] = conf.return_value(c_vendor)
+
+ conf.set_level(base_path)
+ if conf.exists(['client-ip-pool']):
+ if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
+ start = conf.return_value(['client-ip-pool', 'start'])
+ stop = conf.return_value(['client-ip-pool', 'stop'])
+ l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+
+ if conf.exists(['client-ip-pool', 'subnet']):
+ l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet'])
+
+ if conf.exists(['client-ipv6-pool', 'prefix']):
+ l2tp['ip6_column'].append('ip6')
+ for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']):
+ tmp = {
+ 'prefix': prefix,
+ 'mask': '64'
+ }
+
+ if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']):
+ tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask'])
+
+ l2tp['client_ipv6_pool'].append(tmp)
+
+ if conf.exists(['client-ipv6-pool', 'delegate']):
+ l2tp['ip6_column'].append('ip6-db')
+ for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']):
+ tmp = {
+ 'prefix': prefix,
+ 'mask': ''
+ }
+
+ if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'mask']):
+ tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix'])
+
+ l2tp['client_ipv6_delegate_prefix'].append(tmp)
+
+ if conf.exists(['mtu']):
+ l2tp['mtu'] = conf.return_value(['mtu'])
+
+ # gateway address
+ if conf.exists(['gateway-address']):
+ l2tp['gateway_address'] = conf.return_value(['gateway-address'])
+ else:
+ # calculate gw-ip-address
+ if conf.exists(['client-ip-pool', 'start']):
+ # use start ip as gw-ip-address
+ l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
+
+ elif conf.exists(['client-ip-pool', 'subnet']):
+ # use first ip address from first defined pool
+ subnet = conf.return_values(['client-ip-pool', 'subnet'])[0]
+ subnet = ip_network(subnet)
+ l2tp['gateway_address'] = str(list(subnet.hosts())[0])
+
+ # LNS secret
+ if conf.exists(['lns', 'shared-secret']):
+ l2tp['lns_shared_secret'] = conf.return_value(['lns', 'shared-secret'])
+
+ if conf.exists(['ccp-disable']):
+ l2tp[['ccp_disable']] = True
+
+ # PPP options
+ if conf.exists(['idle']):
+ l2tp['ppp_echo_timeout'] = conf.return_value(['idle'])
+
+ if conf.exists(['ppp-options', 'lcp-echo-failure']):
+ l2tp['ppp_echo_failure'] = conf.return_value(['ppp-options', 'lcp-echo-failure'])
+
+ if conf.exists(['ppp-options', 'lcp-echo-interval']):
+ l2tp['ppp_echo_interval'] = conf.return_value(['ppp-options', 'lcp-echo-interval'])
+
+ return l2tp
+
+
+def verify(l2tp):
+ if not l2tp:
+ return None
+
+ if l2tp['auth_mode'] == 'local':
+ if not l2tp['local_users']:
+ raise ConfigError('L2TP local auth mode requires local users to be configured!')
+
+ for user in l2tp['local_users']:
+ if not user['password']:
+ raise ConfigError(f"Password required for user {user['name']}")
+
+ elif l2tp['auth_mode'] == 'radius':
+ if len(l2tp['radius_server']) == 0:
+ raise ConfigError("RADIUS authentication requires at least one server")
+
+ for radius in l2tp['radius_server']:
+ if not radius['key']:
+ raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}")
+
+ # check for the existence of a client ip pool
+ if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']):
+ raise ConfigError(
+ "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
+
+ # check ipv6
+ if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']:
+ raise ConfigError('IPv6 prefix delegation requires client-ipv6-pool prefix')
+
+ for prefix in l2tp['client_ipv6_delegate_prefix']:
+ if not prefix['mask']:
+ raise ConfigError('Delegation-prefix required for individual delegated networks')
+
+ if len(l2tp['wins']) > 2:
+ raise ConfigError('Not more then two IPv4 WINS name-servers can be configured')
+
+ if len(l2tp['dnsv4']) > 2:
+ raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
+
+ if len(l2tp['dnsv6']) > 3:
+ raise ConfigError('Not more then three IPv6 DNS name-servers can be configured')
+
+ return None
+
+
+def generate(l2tp):
+ if not l2tp:
+ return None
+
+ dirname = os.path.dirname(l2tp_conf)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+
+ render(l2tp_conf, 'l2tp/l2tp.config.tmpl', c, trim_blocks=True)
+
+ if l2tp['auth_mode'] == 'local':
+ render(l2tp_chap_secrets, 'l2tp/chap-secrets.tmpl', l2tp)
+ os.chmod(l2tp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
+
+ else:
+ if os.path.exists(l2tp_chap_secrets):
+ os.unlink(l2tp_chap_secrets)
+
+ return None
+
+
+def apply(l2tp):
+ if not l2tp:
+ call('systemctl stop accel-ppp@l2tp.service')
+
+ if os.path.exists(l2tp_conf):
+ os.unlink(l2tp_conf)
+
+ if os.path.exists(l2tp_chap_secrets):
+ os.unlink(l2tp_chap_secrets)
+
+ return None
+
+ call('systemctl restart accel-ppp@l2tp.service')
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index ca0844c50..438731972 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -18,49 +18,24 @@ import os
from time import sleep
from sys import exit
-from socket import socket, AF_INET, SOCK_STREAM
from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
from vyos import ConfigError
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import process_running
-from vyos.util import process_running, cmd, run
-
-pidfile = r'/var/run/accel_sstp.pid'
-sstp_cnf_dir = r'/etc/accel-ppp/sstp'
-chap_secrets = sstp_cnf_dir + '/chap-secrets'
-sstp_conf = sstp_cnf_dir + '/sstp.config'
-
-# config path creation
-if not os.path.exists(sstp_cnf_dir):
- os.makedirs(sstp_cnf_dir)
-
-def chk_con():
- cnt = 0
- s = socket(AF_INET, SOCK_STREAM)
- while True:
- try:
- s.connect(("127.0.0.1", 2005))
- s.close()
- break
- except ConnectionRefusedError:
- sleep(0.5)
- cnt += 1
- if cnt == 100:
- raise("failed to start sstp server")
- break
-
-
-def _accel_cmd(command):
- return run(f'/usr/bin/accel-cmd -p 2005 {command}')
+from vyos.util import call, run
+from vyos.template import render
+
+
+sstp_conf = '/run/accel-pppd/sstp.conf'
+sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets'
default_config_data = {
'local_users' : [],
'auth_mode' : 'local',
- 'auth_proto' : [],
+ 'auth_proto' : ['auth_mschap_v2'],
+ 'chap_secrets_file': sstp_chap_secrets, # used in Jinja2 template
+ 'client_gateway': '',
'radius_server' : [],
'radius_acct_tmo' : '3',
'radius_max_try' : '3',
@@ -77,11 +52,11 @@ default_config_data = {
'client_ip_pool' : [],
'dnsv4' : [],
'mtu' : '',
- 'ppp_mppe' : '',
+ 'ppp_mppe' : 'prefer',
'ppp_echo_failure' : '',
'ppp_echo_interval' : '',
'ppp_echo_timeout' : '',
- 'thread_cnt' : ''
+ 'thread_cnt' : 1
}
def get_config():
@@ -93,10 +68,9 @@ def get_config():
conf.set_level(base_path)
- cpu = int(os.cpu_count()/2)
- if cpu < 1:
- cpu = 1
- sstp['thread_cnt'] = cpu
+ cpu = os.cpu_count()
+ if cpu > 1:
+ sstp['thread_cnt'] = int(cpu/2)
if conf.exists(['authentication', 'mode']):
sstp['auth_mode'] = conf.return_value(['authentication', 'mode'])
@@ -214,6 +188,8 @@ def get_config():
# authentication protocols
conf.set_level(base_path + ['authentication'])
if conf.exists(['protocols']):
+ # clear default list content, now populate with actual CLI values
+ sstp['auth_proto'] = []
auth_mods = {
'pap': 'auth_pap',
'chap': 'auth_chap_md5',
@@ -224,9 +200,6 @@ def get_config():
for proto in conf.return_values(['protocols']):
sstp['auth_proto'].append(auth_mods[proto])
- else:
- sstp['auth_proto'] = ['auth_mschap_v2']
-
#
# read in SSL certs
conf.set_level(base_path + ['ssl'])
@@ -262,7 +235,7 @@ def get_config():
# read in PPP stuff
conf.set_level(base_path + ['ppp-settings'])
if conf.exists('mppe'):
- sstp['ppp_mppe'] = conf.return_value('ppp-settings mppe')
+ sstp['ppp_mppe'] = conf.return_value(['ppp-settings', 'mppe'])
if conf.exists(['lcp-echo-failure']):
sstp['ppp_echo_failure'] = conf.return_value(['lcp-echo-failure'])
@@ -283,7 +256,7 @@ def verify(sstp):
# vertify auth settings
if sstp['auth_mode'] == 'local':
if not sstp['local_users']:
- raise ConfigError('sstp-server authentication local-users required')
+ raise ConfigError('SSTP local auth mode requires local users to be configured!')
for user in sstp['local_users']:
if not user['password']:
@@ -303,7 +276,7 @@ def verify(sstp):
raise ConfigError("Client gateway IP address required")
if len(sstp['dnsv4']) > 2:
- raise ConfigError("Only 2 DNS name-servers can be configured")
+ raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
if not sstp['ssl_ca'] or not sstp['ssl_cert'] or not sstp['ssl_key']:
raise ConfigError('One or more SSL certificates missing')
@@ -326,69 +299,38 @@ def verify(sstp):
raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}")
def generate(sstp):
- if sstp is None:
+ if not sstp:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'sstp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
+ dirname = os.path.dirname(sstp_conf)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
# accel-cmd reload doesn't work so any change results in a restart of the daemon
- tmpl = env.get_template('sstp.config.tmpl')
- config_text = tmpl.render(sstp)
- with open(sstp_conf, 'w') as f:
- f.write(config_text)
+ render(sstp_conf, 'sstp/sstp.config.tmpl', sstp, trim_blocks=True)
if sstp['local_users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- config_text = tmpl.render(sstp)
- with open(chap_secrets, 'w') as f:
- f.write(config_text)
-
- os.chmod(chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
+ render(sstp_chap_secrets, 'sstp/chap-secrets.tmpl', sstp, trim_blocks=True)
+ os.chmod(sstp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
- if os.path.exists(chap_secrets):
- os.unlink(chap_secrets)
+ if os.path.exists(sstp_chap_secrets):
+ os.unlink(sstp_chap_secrets)
return sstp
def apply(sstp):
- if sstp is None:
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- cmd(command)
+ if not sstp:
+ call('systemctl stop accel-ppp@sstp.service')
- if os.path.exists(pidfile):
- os.remove(pidfile)
+ if os.path.exists(sstp_conf):
+ os.unlink(sstp_conf)
- return None
+ if os.path.exists(sstp_chap_secrets):
+ os.unlink(sstp_chap_secrets)
- if not process_running(pidfile):
- if os.path.exists(pidfile):
- os.remove(pidfile)
-
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- command += ' --exec /usr/sbin/accel-pppd'
- # now pass arguments to accel-pppd binary
- command += ' --'
- command += ' -c ' + sstp_conf
- command += ' -p ' + pidfile
- command += ' -d'
- cmd(command)
-
- chk_con()
+ return None
- else:
- _accel_cmd('restart')
+ call('systemctl restart accel-ppp@sstp.service')
if __name__ == '__main__':
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 07466f3aa..eb73293a9 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -18,15 +18,15 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from json import loads
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import Interface
from vyos.util import read_file, cmd
from vyos import ConfigError
+from vyos.template import render
+
config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
@@ -178,16 +178,7 @@ def verify(vrf_config):
return None
def generate(vrf_config):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrf')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('vrf.conf.tmpl')
- config_text = tmpl.render(vrf_config)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'vrf/vrf.conf.tmpl', vrf_config)
return None
def apply(vrf_config):
@@ -195,6 +186,7 @@ def apply(vrf_config):
#
# - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt
# - https://github.com/Mellanox/mlxsw/wiki/Virtual-Routing-and-Forwarding-(VRF)
+ # - https://github.com/Mellanox/mlxsw/wiki/L3-Tunneling
# - https://netdevconf.info/1.1/proceedings/slides/ahern-vrf-tutorial.pdf
# - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index d3e3710d1..b9b0405e2 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -18,16 +18,16 @@ import os
from sys import exit
from ipaddress import ip_address, ip_interface, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address
-from jinja2 import FileSystemLoader, Environment
from json import dumps
from pathlib import Path
import vyos.config
import vyos.keepalived
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
+
daemon_file = "/etc/default/keepalived"
config_file = "/etc/keepalived/keepalived.conf"
@@ -201,11 +201,6 @@ def verify(data):
def generate(data):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrrp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
vrrp_groups, sync_groups = data
# Remove disabled groups from the sync group member lists
@@ -217,16 +212,9 @@ def generate(data):
# Filter out disabled groups
vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups))
- tmpl = env.get_template('keepalived.conf.tmpl')
- config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render()
- with open(daemon_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'vrrp/keepalived.conf.tmpl',
+ {"groups": vrrp_groups, "sync_groups": sync_groups})
+ render(daemon_file, 'vrrp/daemon.tmpl', {})
return None
@@ -242,17 +230,17 @@ def apply(data):
if not vyos.keepalived.vrrp_running():
print("Starting the VRRP process")
- ret = run("sudo systemctl restart keepalived.service")
+ ret = call("sudo systemctl restart keepalived.service")
else:
print("Reloading the VRRP process")
- ret = run("sudo systemctl reload keepalived.service")
+ ret = call("sudo systemctl reload keepalived.service")
if ret != 0:
raise ConfigError("keepalived failed to start")
else:
# VRRP is removed in the commit
print("Stopping the VRRP process")
- run("sudo systemctl stop keepalived.service")
+ call("sudo systemctl stop keepalived.service")
os.unlink(config_file)
return None