summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dhcp-relay/config.tmpl17
-rw-r--r--data/templates/dhcp-server/daemon.tmpl8
-rw-r--r--data/templates/dhcpv6-relay/config.tmpl4
-rw-r--r--data/templates/dhcpv6-server/daemon.tmpl8
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.tmpl3
-rw-r--r--data/templates/l2tp/chap-secrets.tmpl12
-rw-r--r--data/templates/l2tp/l2tp.config.tmpl140
-rw-r--r--data/templates/openvpn/server.conf.tmpl15
-rw-r--r--data/templates/sstp/sstp.config.tmpl2
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl7
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/include/interface-hw-id.xml.i12
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in13
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in54
-rw-r--r--interface-definitions/interfaces-wireless.xml.in13
-rw-r--r--interface-definitions/vpn-l2tp.xml.in176
-rw-r--r--op-mode-definitions/show-ip-bgp.xml342
-rw-r--r--python/vyos/airbag.py5
-rw-r--r--python/vyos/authutils.py2
-rw-r--r--python/vyos/debug.py182
-rw-r--r--python/vyos/ifconfig/__init__.py1
-rw-r--r--python/vyos/ifconfig/control.py13
-rw-r--r--python/vyos/ifconfig/input.py31
-rw-r--r--python/vyos/ifconfig/section.py (renamed from python/vyos/ifconfig/register.py)72
-rw-r--r--python/vyos/ifconfig/tunnel.py14
-rw-r--r--python/vyos/ifconfig/vti.py31
-rw-r--r--python/vyos/remote.py15
-rw-r--r--python/vyos/template.py22
-rw-r--r--python/vyos/util.py144
-rwxr-xr-xsrc/completion/list_interfaces.py44
-rwxr-xr-xsrc/completion/list_openvpn_clients.py4
-rwxr-xr-xsrc/conf_mode/accel_l2tp.py397
-rwxr-xr-xsrc/conf_mode/bcast_relay.py17
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py32
-rwxr-xr-xsrc/conf_mode/dhcp_server.py56
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py27
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py54
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py21
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py44
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py31
-rwxr-xr-xsrc/conf_mode/host_name.py2
-rwxr-xr-xsrc/conf_mode/https.py13
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py14
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py334
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py53
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py134
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py48
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py29
-rwxr-xr-xsrc/conf_mode/le_cert.py13
-rwxr-xr-xsrc/conf_mode/lldp.py19
-rwxr-xr-xsrc/conf_mode/mdns_repeater.py19
-rwxr-xr-xsrc/conf_mode/ntp.py17
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py14
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py14
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py15
-rwxr-xr-xsrc/conf_mode/protocols_pim.py14
-rwxr-xr-xsrc/conf_mode/salt-minion.py13
-rwxr-xr-xsrc/conf_mode/service-ipoe.py20
-rwxr-xr-xsrc/conf_mode/service-pppoe.py19
-rwxr-xr-xsrc/conf_mode/service-router-advert.py13
-rwxr-xr-xsrc/conf_mode/snmp.py31
-rwxr-xr-xsrc/conf_mode/ssh.py13
-rwxr-xr-xsrc/conf_mode/system-login.py42
-rwxr-xr-xsrc/conf_mode/system-syslog.py25
-rwxr-xr-xsrc/conf_mode/system-wifi-regdom.py21
-rwxr-xr-xsrc/conf_mode/tftp_server.py15
-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.py15
-rwxr-xr-xsrc/conf_mode/vrrp.py22
-rwxr-xr-xsrc/etc/init.d/isc-dhcpv4-server113
-rwxr-xr-xsrc/etc/init.d/isc-dhcpv6-relay50
-rwxr-xr-xsrc/etc/init.d/isc-dhcpv6-server113
-rw-r--r--src/etc/systemd/system/hostapd@.service.d/override.conf10
-rw-r--r--src/etc/systemd/system/openvpn@.service.d/override.conf9
-rw-r--r--src/etc/systemd/system/pdns-recursor.service.d/override.conf5
-rw-r--r--src/etc/systemd/system/wpa_supplicant@.service.d/override.conf10
-rwxr-xr-xsrc/migration-scripts/l2tp/2-to-3111
-rwxr-xr-xsrc/op_mode/dns_forwarding_restart.sh2
-rwxr-xr-xsrc/op_mode/dynamic_dns.py9
-rwxr-xr-xsrc/op_mode/flow_accounting_op.py7
-rwxr-xr-xsrc/op_mode/generate_ssh_server_key.py11
-rwxr-xr-xsrc/op_mode/powerctrl.py7
-rwxr-xr-xsrc/op_mode/reset_openvpn.py61
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py4
-rwxr-xr-xsrc/op_mode/show_dhcp.py2
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py2
-rwxr-xr-xsrc/op_mode/version.py3
-rwxr-xr-xsrc/system/keepalived-fifo.py2
-rw-r--r--src/systemd/accel-ppp@.service16
-rw-r--r--src/systemd/ddclient.service14
-rw-r--r--src/systemd/isc-dhcp-relay.service14
-rw-r--r--src/systemd/isc-dhcp-relay6.service14
-rw-r--r--src/systemd/isc-dhcp-server.service19
-rw-r--r--src/systemd/isc-dhcp-server6.service18
-rw-r--r--src/systemd/ppp@.service (renamed from src/etc/systemd/system/ppp@.service)2
-rw-r--r--src/systemd/tftpd@.service2
99 files changed, 2173 insertions, 2087 deletions
diff --git a/data/templates/dhcp-relay/config.tmpl b/data/templates/dhcp-relay/config.tmpl
index 7203ae9fb..b223807cf 100644
--- a/data/templates/dhcp-relay/config.tmpl
+++ b/data/templates/dhcp-relay/config.tmpl
@@ -1,17 +1,4 @@
### Autogenerated by dhcp_relay.py ###
-# Defaults for isc-dhcp-relay initscript
-# sourced by /etc/init.d/isc-dhcp-relay
-
-#
-# This is a POSIX shell fragment
-#
-
-# What servers should the DHCP relay forward requests to?
-SERVERS="{{ server | join(' ') }}"
-
-# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests?
-INTERFACES="{{ interface | join(' ') }}"
-
-# Additional options that are passed to the DHCP relay daemon?
-OPTIONS="-4 {{ options | join(' ') }}"
+# Defaults for isc-dhcp-relay6.service
+OPTIONS="{{ options | join(' ') }} -i {{ interface | join(' -i ') }} {{ server | join(' ') }}"
diff --git a/data/templates/dhcp-server/daemon.tmpl b/data/templates/dhcp-server/daemon.tmpl
deleted file mode 100644
index f88032d38..000000000
--- a/data/templates/dhcp-server/daemon.tmpl
+++ /dev/null
@@ -1,8 +0,0 @@
-### Autogenerated by dhcp_server.py ###
-
-# sourced by /etc/init.d/isc-dhcpv4-server
-
-DHCPD_CONF={{ config_file }}
-DHCPD_PID={{ pid_file }}
-OPTIONS="-4 -lf {{ lease_file }}"
-INTERFACES=""
diff --git a/data/templates/dhcpv6-relay/config.tmpl b/data/templates/dhcpv6-relay/config.tmpl
index 28f7a1a58..55035ae6c 100644
--- a/data/templates/dhcpv6-relay/config.tmpl
+++ b/data/templates/dhcpv6-relay/config.tmpl
@@ -1,4 +1,4 @@
### Autogenerated by dhcpv6_relay.py ###
-# Defaults for isc-dhcpv6-relay initscript sourced by /etc/init.d/isc-dhcpv6-relay
-OPTIONS="-6 -l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}"
+# Defaults for isc-dhcp-relay6.service
+OPTIONS="-l {{ listen_addr | join(' -l ') }} -u {{ upstream_addr | join(' -u ') }} {{ options | join(' ') }}"
diff --git a/data/templates/dhcpv6-server/daemon.tmpl b/data/templates/dhcpv6-server/daemon.tmpl
deleted file mode 100644
index a4967e7c3..000000000
--- a/data/templates/dhcpv6-server/daemon.tmpl
+++ /dev/null
@@ -1,8 +0,0 @@
-### Autogenerated by dhcpv6_server.py ###
-
-# sourced by /etc/init.d/isc-dhcpv6-server
-
-DHCPD_CONF={{ config_file }}
-DHCPD_PID={{ pid_file }}
-OPTIONS="-6 -lf {{ lease_file }}"
-INTERFACES=""
diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl
index 22cb38f4e..9c7219230 100644
--- a/data/templates/dynamic-dns/ddclient.conf.tmpl
+++ b/data/templates/dynamic-dns/ddclient.conf.tmpl
@@ -1,10 +1,7 @@
-
### Autogenerated by dynamic_dns.py ###
daemon=1m
syslog=yes
ssl=yes
-pid={{ pid_file }}
-cache={{ cache_file }}
{% for interface in interfaces -%}
diff --git a/data/templates/l2tp/chap-secrets.tmpl b/data/templates/l2tp/chap-secrets.tmpl
index 0db295fdc..dd00d7bd0 100644
--- a/data/templates/l2tp/chap-secrets.tmpl
+++ b/data/templates/l2tp/chap-secrets.tmpl
@@ -1,10 +1,10 @@
-# username server password acceptable local IP addresses shaper
-{% for user in authentication['local-users'] %}
-{% if authentication['local-users'][user]['state'] == 'enabled' %}
-{% if authentication['local-users'][user]['upload'] and authentication['local-users'][user]['download'] %}
-{{ "%-12s" | format(user) }} * {{ "%-16s" | format(authentication['local-users'][user]['passwd']) }} {{ "%-16s" | format(authentication['local-users'][user]['ip']) }} {{ authentication['local-users'][user]['download'] }} / {{ authentication['local-users'][user]['upload'] }}
+# username server password acceptable local IP addresses shaper
+{% for user in local_users %}
+{% if user.state == 'enabled' %}
+{% if user.upload and user.download %}
+{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }} {{ user.download }} / {{ user.upload }}
{% else %}
-{{ "%-12s" | format(user) }} * {{ "%-16s" | format(authentication['local-users'][user]['passwd']) }} {{ "%-16s" | format(authentication['local-users'][user]['ip']) }}
+{{ "%-12s" | format(user.name) }} * {{ "%-16s" | format(user.password) }} {{ "%-16s" | format(user.ip) }}
{% endif %}
{% endif %}
{% endfor %}
diff --git a/data/templates/l2tp/l2tp.config.tmpl b/data/templates/l2tp/l2tp.config.tmpl
index b8637e256..ba78cadcd 100644
--- a/data/templates/l2tp/l2tp.config.tmpl
+++ b/data/templates/l2tp/l2tp.config.tmpl
@@ -3,12 +3,14 @@
log_syslog
l2tp
chap-secrets
-{% for proto in authentication['auth_proto']: %}
+{% for proto in auth_proto: %}
{{proto}}
{% endfor%}
-{% if authentication['mode'] == 'radius' %}
+
+{% if auth_mode == 'radius' %}
radius
{% endif -%}
+
ippool
shaper
ipv6pool
@@ -23,52 +25,46 @@ syslog=accel-l2tp,daemon
copy=1
level=5
-{% if dns %}
+{% if dnsv4 %}
[dns]
-{% if dns[0] %}
-dns1={{dns[0]}}
-{% endif %}
-{% if dns[1] %}
-dns2={{dns[1]}}
+{% for dns in dnsv4 -%}
+dns{{ loop.index }}={{ dns }}
+{% endfor -%}
{% endif %}
-{% endif -%}
{% if dnsv6 %}
[ipv6-dns]
-{% for srv in dnsv6: %}
-{{srv}}
-{% endfor %}
+{% for dns in dnsv6 -%}
+{{ dns }}
+{% endfor -%}
{% endif %}
{% if wins %}
[wins]
-{% if wins[0] %}
-wins1={{wins[0]}}
-{% endif %}
-{% if wins[1] %}
-wins2={{wins[1]}}
+{% for server in wins -%}
+wins{{ loop.index }}={{ server }}
+{% endfor -%}
{% endif %}
-{% endif -%}
[l2tp]
verbose=1
ifname=l2tp%d
-ppp-max-mtu={{mtu}}
-mppe={{authentication['mppe']}}
+ppp-max-mtu={{ mtu }}
+mppe={{ ppp_mppe }}
{% if outside_addr %}
-bind={{outside_addr}}
+bind={{ outside_addr }}
{% endif %}
{% if lns_shared_secret %}
-secret={{lns_shared_secret}}
+secret={{ lns_shared_secret }}
{% endif %}
[client-ip-range]
0.0.0.0/0
-{% if (client_ip_pool) or (client_ip_subnets) %}
+{% if client_ip_pool or client_ip_subnets %}
[ip-pool]
{% if client_ip_pool %}
-{{client_ip_pool}}
+{{ client_ip_pool }}
{% endif -%}
{% if client_ip_subnets %}
{% for sn in client_ip_subnets %}
@@ -77,34 +73,41 @@ secret={{lns_shared_secret}}
{% endif %}
{% endif %}
{% if gateway_address %}
-gw-ip-address={{gateway_address}}
+gw-ip-address={{ gateway_address }}
{% endif %}
-{% if authentication['mode'] == 'local' %}
+{% if auth_mode == 'local' %}
[chap-secrets]
-chap-secrets=/etc/accel-ppp/l2tp/chap-secrets
-{% if gateway_address %}
-gw-ip-address={{gateway_address}}
-{% endif %}
+chap-secrets={{ chap_secrets_file }}
+{% elif auth_mode == 'radius' %}
+[radius]
+verbose=1
+{% for r in radius_server %}
+server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }}
+{% endfor -%}
{% endif %}
+acct-timeout={{ radius_acct_tmo }}
+timeout={{ radius_timeout }}
+max-try={{ radius_max_try }}
+
+{% if radius_nas_id %}
+nas-identifier={{ radius_nas_id }}
+{% endif -%}
+{% if radius_nas_ip %}
+nas-ip-address={{ radius_nas_ip }}
+{% endif -%}
+{% if radius_source_address %}
+bind={{ radius_source_address }}
+{% endif -%}
+
[ppp]
verbose=1
check-ip=1
single-session=replace
-{% if idle_timeout %}
-lcp-echo-timeout={{idle_timeout}}
-{% endif %}
-{% if ppp_options['lcp-echo-interval'] %}
-lcp-echo-interval={{ppp_options['lcp-echo-interval']}}
-{% else %}
-lcp-echo-interval=30
-{% endif %}
-{% if ppp_options['lcp-echo-failure'] %}
-lcp-echo-failure={{ppp_options['lcp-echo-failure']}}
-{% else %}
-lcp-echo-failure=3
-{% endif %}
+lcp-echo-timeout={{ ppp_echo_timeout }}
+lcp-echo-interval={{ ppp_echo_interval }}
+lcp-echo-failure={{ ppp_echo_failure }}
{% if ccp_disable %}
ccp=0
{% endif %}
@@ -112,62 +115,33 @@ ccp=0
ipv6=allow
{% endif %}
-{% if authentication['mode'] == 'radius' %}
-[radius]
-{% for rsrv in authentication['radiussrv']: %}
-server={{rsrv}},{{authentication['radiussrv'][rsrv]['secret']}},\
-req-limit={{authentication['radiussrv'][rsrv]['req-limit']}},\
-fail-time={{authentication['radiussrv'][rsrv]['fail-time']}}
-{% endfor %}
-{% if authentication['radiusopt']['timeout'] %}
-timeout={{authentication['radiusopt']['timeout']}}
-{% endif %}
-{% if authentication['radiusopt']['acct-timeout'] %}
-acct-timeout={{authentication['radiusopt']['acct-timeout']}}
-{% endif %}
-{% if authentication['radiusopt']['max-try'] %}
-max-try={{authentication['radiusopt']['max-try']}}
-{% endif %}
-{% if authentication['radiusopt']['nas-id'] %}
-nas-identifier={{authentication['radiusopt']['nas-id']}}
-{% endif %}
-{% if authentication['radius_source_address'] %}
-nas-ip-address={{authentication['radius_source_address']}}
-{% endif -%}
-{% if authentication['radiusopt']['dae-srv'] %}
-dae-server={{authentication['radiusopt']['dae-srv']['ip-addr']}}:\
-{{authentication['radiusopt']['dae-srv']['port']}},\
-{{authentication['radiusopt']['dae-srv']['secret']}}
-{% endif -%}
-gw-ip-address={{gateway_address}}
-verbose=1
-{% endif -%}
{% if client_ipv6_pool %}
[ipv6-pool]
-{% for prfx in client_ipv6_pool.prefix: %}
-{{prfx}}
+{% for p in client_ipv6_pool %}
+{{ p.prefix }},{{ p.mask }}
{% endfor %}
-{% for prfx in client_ipv6_pool.delegate_prefix: %}
-delegate={{prfx}}
+{% for p in client_ipv6_delegate_prefix %}
+delegate={{ p.prefix }},{{ p.mask }}
{% endfor %}
+
{% endif %}
-{% if client_ipv6_pool['delegate_prefix'] %}
+{% if client_ipv6_delegate_prefix %}
[ipv6-dhcp]
verbose=1
{% endif %}
-{% if authentication['radiusopt']['shaper'] %}
+{% if radius_shaper_attr %}
[shaper]
verbose=1
-attr={{authentication['radiusopt']['shaper']['attr']}}
-{% if authentication['radiusopt']['shaper']['vendor'] %}
-vendor={{authentication['radiusopt']['shaper']['vendor']}}
+attr={{ radius_shaper_attr }}
+{% if radius_shaper_vendor %}
+vendor={{ radius_shaper_vendor }}
{% endif -%}
{% endif %}
[cli]
tcp=127.0.0.1:2004
-sessions-columns=ifname,username,calling-sid,ip,{{ip6_column}}{{ip6_dp_column}}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime
+sessions-columns=ifname,username,calling-sid,ip,{{ ip6_column | join(',') }}{{ ',' if ip6_column }}rate-limit,type,comp,state,rx-bytes,tx-bytes,uptime
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
index d0af3d2e3..a9dacd36e 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -9,8 +9,6 @@
{% endif -%}
verb 3
-status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30
-writepid /var/run/openvpn/{{ intf }}.pid
user {{ uid }}
group {{ gid }}
@@ -73,13 +71,18 @@ nobind
#
{%- if server_topology %}
-topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %}
+topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %}
{%- endif %}
{%- if bridge_member %}
-server-bridge nogw
+mode server
+tls-server
{%- else %}
-server {{ server_subnet }}
+server {{ server_subnet }}{% if server_pool_start %} nopool{% endif %}
+{%- endif %}
+
+{%- if server_pool_start %}
+ifconfig-pool {{ server_pool_start }} {{ server_pool_stop }}{% if server_pool_netmask %} {{ server_pool_netmask }}{% endif %}
{%- endif %}
{%- if server_max_conn %}
@@ -87,7 +90,7 @@ max-clients {{ server_max_conn }}
{%- endif %}
{%- if client %}
-client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
+client-config-dir /run/openvpn/ccd/{{ intf }}
{%- endif %}
{%- if server_reject_unconfigured %}
diff --git a/data/templates/sstp/sstp.config.tmpl b/data/templates/sstp/sstp.config.tmpl
index 6c09c52ad..acdb6c76b 100644
--- a/data/templates/sstp/sstp.config.tmpl
+++ b/data/templates/sstp/sstp.config.tmpl
@@ -53,7 +53,7 @@ dns{{ loop.index }}={{ dns }}
{% if auth_mode == 'local' %}
[chap-secrets]
-chap-secrets=/etc/accel-ppp/sstp/chap-secrets
+chap-secrets={{ chap_secrets_file }}
{% elif auth_mode == 'radius' %}
[radius]
verbose=1
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
index 031fb6c90..e2fb9ca8f 100644
--- a/data/templates/wifi/hostapd.conf.tmpl
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -73,7 +73,6 @@ channel={{ channel }}
# offloaded ACS is used.
{% if 'n' in mode -%}
hw_mode=g
-ieee80211n=1
{% elif 'ac' in mode -%}
hw_mode=a
ieee80211h=1
@@ -421,6 +420,12 @@ vht_capab=
ieee80211n=0
# Require stations to support VHT PHY (reject association if they do not)
require_vht=1
+{% else -%}
+{% if 'n' in mode or 'ac' in mode -%}
+ieee80211n=1
+{% else -%}
+ieee80211n=0
+{%- endif %}
{% endif %}
{% if cap_vht_center_freq_1 -%}
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 5004d111f..dd8eebc0b 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,5 +1,4 @@
etc/dhcp
-etc/init.d
etc/ppp
etc/rsyslog.d
etc/systemd
diff --git a/interface-definitions/include/interface-hw-id.xml.i b/interface-definitions/include/interface-hw-id.xml.i
new file mode 100644
index 000000000..cefc9f0a0
--- /dev/null
+++ b/interface-definitions/include/interface-hw-id.xml.i
@@ -0,0 +1,12 @@
+<leafNode name="mac">
+ <properties>
+ <help>Associate Ethernet Interface with given Media Access Control (MAC) address</help>
+ <valueHelp>
+ <format>h:h:h:h:h:h</format>
+ <description>Hardware Media Access Control (MAC) address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index f8ec26d04..89669f966 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -56,18 +56,7 @@
<constraintErrorMessage>duplex must be auto, half or full</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="hw-id">
- <properties>
- <help>Media Access Control (MAC) address</help>
- <valueHelp>
- <format>h:h:h:h:h:h</format>
- <description>Hardware (MAC) address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/interface-hw-id.xml.i>
<node name="ip">
<children>
#include <include/interface-arp-cache-timeout.xml.i>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 92bac3fab..d926876f7 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -444,6 +444,52 @@
</leafNode>
</children>
</tagNode>
+ <node name="client-ip-pool">
+ <properties>
+ <help>Pool of client IP addresses</help>
+ </properties>
+ <children>
+ <leafNode name="start">
+ <properties>
+ <help>First IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Last IP address in the pool</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="subnet-mask">
+ <properties>
+ <help>Subnet mask pushed to dynamic clients.
+ If not set the server subnet mask will be used.
+ Only used with topology subnet or device type tap.
+ Not used with bridged interfaces.</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 subnet mask</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="domain-name">
<properties>
<help>DNS suffix to be pushed to all clients</help>
@@ -501,7 +547,7 @@
<help>Server-mode subnet (from which client IPs are allocated)</help>
<valueHelp>
<format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
+ <description>IPv4 network and prefix length</description>
</valueHelp>
<constraint>
<validator name="ipv4-prefix"/>
@@ -512,9 +558,13 @@
<properties>
<help>Topology for clients</help>
<completionHelp>
- <list>point-to-point subnet</list>
+ <list>net30 point-to-point subnet</list>
</completionHelp>
<valueHelp>
+ <format>net30</format>
+ <description>net30 topology</description>
+ </valueHelp>
+ <valueHelp>
<format>point-to-point</format>
<description>Point-to-point topology</description>
</valueHelp>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index 194669f77..a5c6315fa 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -476,18 +476,7 @@
#include <include/ipv6-dup-addr-detect-transmits.xml.i>
</children>
</node>
- <leafNode name="hw-id">
- <properties>
- <help>Media Access Control (MAC) address</help>
- <valueHelp>
- <format>h:h:h:h:h:h</format>
- <description>Hardware (MAC) address</description>
- </valueHelp>
- <constraint>
- <validator name="mac-address"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/interface-hw-id.xml.i>
<leafNode name="isolate-stations">
<properties>
<help>Isolate stations on the AP so they cannot see each other</help>
diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index 7fc844054..d4286a810 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="vpn">
<children>
- <node name="l2tp" owner="${vyos_conf_scripts_dir}/accel_l2tp.py">
+ <node name="l2tp" owner="${vyos_conf_scripts_dir}/vpn_l2tp.py">
<properties>
<help>L2TP Virtual Private Network (VPN)</help>
</properties>
@@ -36,48 +36,22 @@
</constraint>
</properties>
</leafNode>
- <node name="dns-servers">
+ <leafNode name="name-server">
<properties>
- <help>IPv4 Domain Name Service (DNS) server</help>
- </properties>
- <children>
- <leafNode name="server-1">
- <properties>
- <help>Primary DNS server</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="server-2">
- <properties>
- <help>Secondary DNS server</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- <leafNode name="dnsv6-servers">
- <properties>
- <help>IPv6 Domain Name Service (DNS) server</help>
+ <help>Domain Name Server (DNS) propagated to client</help>
<valueHelp>
- <format>ipv6</format>
- <description>IPv6 DNS address</description>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Domain Name Server (DNS) IPv6 address</description>
</valueHelp>
<constraint>
+ <validator name="ipv4-address"/>
<validator name="ipv6-address"/>
</constraint>
- <multi />
+ <multi/>
</properties>
</leafNode>
<node name="lns">
@@ -208,29 +182,19 @@
</leafNode>
</children>
</node>
- <node name="wins-servers">
+ <leafNode name="wins-server">
<properties>
- <help>Windows Internet Name Service (WINS) server settings</help>
+ <help>Windows Internet Name Service (WINS) servers propagated to client</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <multi/>
</properties>
- <children>
- <leafNode name="server-1">
- <properties>
- <help>Primary WINS server</help>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="server-2">
- <properties>
- <help>Secondary WINS server</help>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
+ </leafNode>
<node name="client-ip-pool">
<properties>
<help>Pool of client IP addresses (must be within a /24)</help>
@@ -273,26 +237,58 @@
<help>Pool of client IPv6 addresses</help>
</properties>
<children>
- <leafNode name="prefix">
+ <tagNode name="prefix">
<properties>
- <help>IPV6 prefix delegation</help>
+ <help>Pool of addresses used to assign to clients</help>
<valueHelp>
- <format>ipv6prefix/mask,prefix_len</format>
- <description>e.g.: fc00:0:1::/48,64 - divides prefix into /64 subnets for clients</description>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
</valueHelp>
- <multi />
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
</properties>
- </leafNode>
- <leafNode name="delegate-prefix">
+ <children>
+ <leafNode name="mask">
+ <properties>
+ <help>Prefix length used for individual client</help>
+ <valueHelp>
+ <format>&lt;48-128&gt;</format>
+ <description>Client prefix length (default: 64)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 48-128"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="delegate">
<properties>
- <help>DHCPv6 prefix delegation - rfc3633</help>
+ <help>Subnet used to delegate prefix through DHCPv6-PD (RFC3633)</help>
<valueHelp>
- <format>ipv6prefix/mask,prefix_len</format>
- <description>Delegate to clients through DHCPv6 prefix delegation - rfc3633</description>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
</valueHelp>
- <multi />
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
</properties>
- </leafNode>
+ <children>
+ <leafNode name="delegation-prefix">
+ <properties>
+ <help>Prefix length delegated to client</help>
+ <valueHelp>
+ <format>&lt;32-64&gt;</format>
+ <description>Delegated prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 32-64"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
</children>
</node>
<leafNode name="description">
@@ -445,46 +441,26 @@
</tagNode>
</children>
</node>
+ #include <include/radius-server.xml.i>
<node name="radius">
- <properties>
- <help>RADIUS specific configuration</help>
- </properties>
<children>
<tagNode name="server">
- <properties>
- <help>IP address of RADIUS server</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of RADIUS server</description>
- </valueHelp>
- </properties>
<children>
- <leafNode name="key">
- <properties>
- <help>Key for accessing the specified server</help>
- </properties>
- </leafNode>
- <leafNode name="req-limit">
- <properties>
- <help>Maximum number of simultaneous requests to server (default: unlimited)</help>
- </properties>
- </leafNode>
<leafNode name="fail-time">
<properties>
- <help>If server doesn not responds mark it unavailable for this time (seconds)</help>
+ <help>Mark server unavailable for &lt;n&gt; seconds on failure</help>
+ <valueHelp>
+ <format>0-600</format>
+ <description>Fail time penalty</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-600"/>
+ </constraint>
+ <constraintErrorMessage>Fail time must be between 0 and 600 seconds</constraintErrorMessage>
</properties>
</leafNode>
</children>
</tagNode>
- <leafNode name="source-address">
- <properties>
- <help>Local RADIUS client address from which packets are sent.</help>
- <valueHelp>
- <format>&lt;x.x.x.x&gt;</format>
- <description>Local RADIUS client address from which packets are sent</description>
- </valueHelp>
- </properties>
- </leafNode>
<leafNode name="timeout">
<properties>
<help>Timeout to wait response from server (seconds)</help>
diff --git a/op-mode-definitions/show-ip-bgp.xml b/op-mode-definitions/show-ip-bgp.xml
new file mode 100644
index 000000000..5eb2ae56e
--- /dev/null
+++ b/op-mode-definitions/show-ip-bgp.xml
@@ -0,0 +1,342 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="ip">
+ <children>
+ <node name="bgp">
+ <properties>
+ <help>Show Border Gateway Protocol (BGP) information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp"</command>
+ <children>
+ <leafNode name="attribute-info">
+ <properties>
+ <help>Show BGP attribute information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp attribute-info"</command>
+ </leafNode>
+ <leafNode name="cidr-only">
+ <properties>
+ <help>Display only routes with non-natural netmasks</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp cidr-only"</command>
+ </leafNode>
+ <node name="community">
+ <properties>
+ <help>Show BGP routes matching the communities</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp community"</command>
+ </node>
+ <tagNode name="community">
+ <properties>
+ <help>Display routes matching the specified communities</help>
+ <completionHelp>
+ <list>&lt;AA:NN&gt; local-AS no-advertise no-export</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp community $5"</command>
+ </tagNode>
+ <leafNode name="community-info">
+ <properties>
+ <help>List all bgp community information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp community-info"</command>
+ </leafNode>
+ <tagNode name="community-list">
+ <properties>
+ <help>Show BGP routes matching specified community list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp community-list $5"</command>
+ <children>
+ <leafNode name="exact-match">
+ <properties>
+ <help>Show BGP routes exactly matching specified community list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp community-list $5 exact-match"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="dampened-paths">
+ <properties>
+ <help>Show dampened BGP paths</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp dampening dampened-paths"</command>
+ </leafNode>
+ <tagNode name="filter-list">
+ <properties>
+ <help>Show BGP information for specified word</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp filter-list $5"</command>
+ </tagNode>
+ <leafNode name="flap-statistics">
+ <properties>
+ <help>Show flap statistics of routes</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp dampening flap-statistics"</command>
+ </leafNode>
+ <node name="ipv4">
+ <properties>
+ <help>Show BGP IPv4 information</help>
+ </properties>
+ <children>
+ <node name="unicast">
+ <properties>
+ <help>Show BGP IPv4 unicast information</help>
+ </properties>
+ <children>
+ <leafNode name="cidr-only">
+ <properties>
+ <help>Display only routes with non-natural netmasks</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast cidr-only"</command>
+ </leafNode>
+ <node name="community"> <!-- START new code -->
+ <properties>
+ <help>Show BGP routes matching the communities</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community"</command>
+ </node>
+ <tagNode name="community">
+ <properties>
+ <help>Display routes matching the specified communities</help>
+ <completionHelp>
+ <list>&lt;AA:NN&gt; local-AS no-advertise no-export</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community $7"</command>
+ </tagNode>
+ <tagNode name="community-list">
+ <properties>
+ <help>Show BGP routes matching specified community list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community-list $7"</command>
+ <children>
+ <leafNode name="exact-match">
+ <properties>
+ <help>Show BGP routes exactly matching specified community list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast community-list $7 exact-match"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="filter-list">
+ <properties>
+ <help>Show BGP information for specified word</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp filter-list $5"</command>
+ </tagNode>
+ <tagNode name="neighbors">
+ <properties>
+ <help>Show detailed BGP IPv4 unicast neighbor information</help>
+ <completionHelp>
+ <script>vtysh -c "show ip bgp ipv4 unicast summary" | awk '{print $1}' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbors $7"</command>
+ <children>
+ <leafNode name="advertised-routes">
+ <properties>
+ <help>Show routes advertised to a BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 advertised-routes"</command>
+ </leafNode>
+ <leafNode name="prefix-counts">
+ <properties>
+ <help>Show detailed prefix count information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 prefix-counts"</command>
+ </leafNode>
+ <leafNode name="received-routes">
+ <properties>
+ <help>Show the received routes from neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 received-routes"</command>
+ </leafNode>
+ <leafNode name="routes">
+ <properties>
+ <help>Show routes learned from neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast neighbor $7 routes"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="paths">
+ <properties>
+ <help>Show BGP path information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast paths"</command>
+ </leafNode>
+ <tagNode name="prefix-list">
+ <properties>
+ <help>Show BGP routes matching the specified prefix list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast prefix-list $7"</command>
+ </tagNode>
+ <tagNode name="regexp">
+ <properties>
+ <help>Show BGP routes matching the specified AS path regular expression</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp ipv4 unicast regexp $5"</command>
+ </tagNode>
+ <tagNode name="route-map">
+ <properties>
+ <help>Show BGP routes matching the specified route map</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp route-map $5"</command>
+ </tagNode>
+ <leafNode name="summary">
+ <properties>
+ <help>Show summary of BGP information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp summary"</command>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="unicast">
+ <properties>
+ <help>Show BGP information for specified IP address or prefix</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;x.x.x.x/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp $6"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="large-community">
+ <properties>
+ <help>Show BGP routes matching the specified large-communities</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp large-community"</command>
+ </node>
+ <leafNode name="large-community-info">
+ <properties>
+ <help>Show BGP large-community information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp large-community-info"</command>
+ </leafNode>
+ <tagNode name="large-community-list">
+ <properties>
+ <help>Show BGP routes matching the specified large-community list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp large-community-list $5"</command>
+ </tagNode>
+ <leafNode name="memory">
+ <properties>
+ <help>Show BGP memory usage</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp memory"</command>
+ </leafNode>
+ <tagNode name="neighbors">
+ <properties>
+ <help>Show detailed BGP IPv4 unicast neighbor information</help>
+ <completionHelp>
+ <script>vtysh -c "show ip bgp summary" | awk '{print $1}' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b"</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbors $5"</command>
+ <children>
+ <leafNode name="advertised-routes">
+ <properties>
+ <help>Show routes advertised to a BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 advertised-routes"</command>
+ </leafNode>
+ <leafNode name="dampened-routes">
+ <properties>
+ <help>Show dampened routes received from BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 dampened-routes"</command>
+ </leafNode>
+ <leafNode name="flap-statistics">
+ <properties>
+ <help>Show flap statistics of the routes learned from BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 flap-statistics"</command>
+ </leafNode>
+ <leafNode name="prefix-counts">
+ <properties>
+ <help>Show detailed prefix count information for BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 prefix-counts"</command>
+ </leafNode>
+ <node name="received">
+ <properties>
+ <help>Show information received from BGP neighbor</help>
+ </properties>
+ <children>
+ <leafNode name="prefix-filter">
+ <properties>
+ <help>Show prefixlist filter</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 received prefix-filter"</command>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="received-routes">
+ <properties>
+ <help>Show received routes from BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 received-routes"</command>
+ </leafNode>
+ <leafNode name="routes">
+ <properties>
+ <help>Show routes learned from BGP neighbor</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp neighbor $5 routes"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="paths">
+ <properties>
+ <help>Show BGP path information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp paths"</command>
+ </leafNode>
+ <tagNode name="prefix-list">
+ <properties>
+ <help>Show BGP routes matching the specified prefix list</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp prefix-list $5"</command>
+ </tagNode>
+ <tagNode name="regexp">
+ <properties>
+ <help>Show BGP routes matching the specified AS path regular expression</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp regexp $5"</command>
+ </tagNode>
+ <tagNode name="route-map">
+ <properties>
+ <help>Show BGP routes matching the specified route map</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp route-map $5"</command>
+ </tagNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show summary of BGP information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp statistics"</command>
+ </leafNode>
+ <leafNode name="summary">
+ <properties>
+ <help>Show summary of BGP information</help>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp summary"</command>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="bgp">
+ <properties>
+ <help>Show BGP information for specified IP address or prefix</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt; &lt;x.x.x.x/x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/vtysh -c "show ip bgp $4"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
index 664974d5f..b0565192d 100644
--- a/python/vyos/airbag.py
+++ b/python/vyos/airbag.py
@@ -19,10 +19,10 @@ import logging
import logging.handlers
from datetime import datetime
+from vyos import debug
from vyos.config import Config
from vyos.version import get_version
from vyos.util import run
-from vyos.util import debug
# we allow to disable the extra logging
@@ -77,8 +77,7 @@ def bug_report(dtype, value, trace):
# reach the end of __main__ and was not intercepted
def intercepter(dtype, value, trace):
bug_report(dtype, value, trace)
- # debug returns either '' or 'developer' if debuging is enabled
- if debug('developer'):
+ if debug.enabled('developer'):
import pdb
pdb.pm()
diff --git a/python/vyos/authutils.py b/python/vyos/authutils.py
index 90a46ffb4..66b5f4a74 100644
--- a/python/vyos/authutils.py
+++ b/python/vyos/authutils.py
@@ -22,7 +22,7 @@ def make_password_hash(password):
""" Makes a password hash for /etc/shadow using mkpasswd """
mkpassword = 'mkpasswd --method=sha-512 --stdin'
- return cmd(mkpassword, input=password.encode(), timeout=5)
+ return cmd(mkpassword, input=password, timeout=5)
def split_ssh_public_key(key_string, defaultname=""):
""" Splits an SSH public key into its components """
diff --git a/python/vyos/debug.py b/python/vyos/debug.py
new file mode 100644
index 000000000..20090fb85
--- /dev/null
+++ b/python/vyos/debug.py
@@ -0,0 +1,182 @@
+# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+
+
+def message(message, flag='', destination=sys.stdout):
+ """
+ print a debug message line on stdout if debugging is enabled for the flag
+ also log it to a file if the flag 'log' is enabled
+
+ message: the message to print
+ flag: which flag must be set for it to print
+ destination: which file like object to write to (default: sys.stdout)
+
+ returns if any message was logged or not
+ """
+ enable = enabled(flag)
+ if enable:
+ destination.write(_format(flag,message))
+
+ # the log flag is special as it logs all the commands
+ # executed to a log
+ logfile = _logfile('log', '/tmp/developer-log')
+ if not logfile:
+ return enable
+
+ try:
+ # at boot the file is created as root:vyattacfg
+ # at runtime the file is created as user:vyattacfg
+ # the default permission are 644
+ mask = os.umask(0o113)
+
+ with open(logfile, 'a') as f:
+ f.write(_format('log', message))
+ finally:
+ os.umask(mask)
+
+ return enable
+
+
+def enabled(flag):
+ """
+ a flag can be set by touching the file in /tmp or /config
+
+ The current flags are:
+ - developer: the code will drop into PBD on un-handled exception
+ - log: the code will log all command to a file
+ - ifconfig: when modifying an interface,
+ prints command with result and sysfs access on stdout for interface
+ - command: print command run with result
+
+ Having the flag setup on the filesystem is required to have
+ debuging at boot time, however, setting the flag via environment
+ does not require a seek to the filesystem and is more efficient
+ it can be done on the shell on via .bashrc for the user
+
+ The function returns an empty string if the flag was not set otherwise
+ the function returns either the file or environment name used to set it up
+ """
+
+ # this is to force all new flags to be registered here to be
+ # documented both here and a reminder to update readthedocs :-)
+ if flag not in ['developer', 'log', 'ifconfig', 'command']:
+ return ''
+
+ return _fromenv(flag) or _fromfile(flag)
+
+
+def _format(flag, message):
+ """
+ format a log message
+ """
+ return f'DEBUG/{flag.upper():<7} {message}\n'
+
+
+def _fromenv(flag):
+ """
+ check if debugging is set for this flag via environment
+
+ For a given debug flag named "test"
+ The presence of the environment VYOS_TEST_DEBUG (uppercase) enables it
+
+ return empty string if not
+ return content of env value it is
+ """
+
+ flagname = f'VYOS_{flag.upper()}_DEBUG'
+ flagenv = os.environ.get(flagname, None)
+
+ if flagenv is None:
+ return ''
+ return flagenv
+
+
+def _fromfile(flag):
+ """
+ Check if debug exist for a given debug flag name
+
+ Check is a debug flag was set by the user. the flag can be set either:
+ - in /tmp for a non-persistent presence between reboot
+ - in /config for always on (an existence at boot time)
+
+ For a given debug flag named "test"
+ The presence of the file vyos.test.debug (all lowercase) enables it
+
+ The function returns an empty string if the flag was not set otherwise
+ the function returns the full flagname
+ """
+
+ for folder in ('/tmp', '/config'):
+ flagfile = f'{folder}/vyos.{flag}.debug'
+ if os.path.isfile(flagfile):
+ return flagfile
+
+ return ''
+
+
+def _contentenv(flag):
+ return os.environ.get(f'VYOS_{flag.upper()}_DEBUG', '').strip()
+
+
+def _contentfile(flag):
+ """
+ Check if debug exist for a given debug flag name
+
+ Check is a debug flag was set by the user. the flag can be set either:
+ - in /tmp for a non-persistent presence between reboot
+ - in /config for always on (an existence at boot time)
+
+ For a given debug flag named "test"
+ The presence of the file vyos.test.debug (all lowercase) enables it
+
+ The function returns an empty string if the flag was not set otherwise
+ the function returns the full flagname
+ """
+
+ for folder in ('/tmp', '/config'):
+ flagfile = f'{folder}/vyos.{flag}.debug'
+ if not os.path.isfile(flagfile):
+ continue
+ with open(flagfile) as f:
+ return f.readline().strip()
+
+ return ''
+
+
+def _logfile(flag, default):
+ """
+ return the name of the file to use for logging when the flag 'log' is set
+ if it could not be established or the location is invalid it returns
+ an empty string
+ """
+
+ # For log we return the location of the log file
+ log_location = _contentenv(flag) or _contentfile(flag)
+
+ # it was not set
+ if not log_location:
+ return ''
+
+ # Make sure that the logs can only be in /tmp, /var/log, or /tmp
+ if not log_location.startswith('/tmp/') and \
+ not log_location.startswith('/config/') and \
+ not log_location.startswith('/var/log/'):
+ return default
+ if '..' in log_location:
+ return default
+ return log_location
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 1f9956af0..cd1696ca1 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -14,6 +14,7 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+from vyos.ifconfig.section import Section
from vyos.ifconfig.interface import Interface
from vyos.ifconfig.bond import BondIf
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index c7a2fa2d6..7bb63beed 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -16,12 +16,13 @@
import os
-from vyos.util import debug, debug_msg
-from vyos.util import popen, cmd
-from vyos.ifconfig.register import Register
+from vyos import debug
+from vyos.util import popen
+from vyos.util import cmd
+from vyos.ifconfig.section import Section
-class Control(Register):
+class Control(Section):
_command_get = {}
_command_set = {}
@@ -35,10 +36,10 @@ class Control(Register):
# if debug is not explicitely disabled the the config, enable it
self.debug = ''
if kargs.get('debug', True):
- self.debug = debug('ifconfig')
+ self.debug = debug.enabled('ifconfig')
def _debug_msg (self, message):
- return debug_msg(message, self.debug)
+ return debug.message(message, self.debug)
def _popen(self, command):
return popen(command, self.debug)
diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py
new file mode 100644
index 000000000..bfab36335
--- /dev/null
+++ b/python/vyos/ifconfig/input.py
@@ -0,0 +1,31 @@
+# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+@Interface.register
+class InputIf(Interface):
+ default = {
+ 'type': '',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'input',
+ 'prefixes': ['ifb', ],
+ },
+ }
diff --git a/python/vyos/ifconfig/register.py b/python/vyos/ifconfig/section.py
index c90782b70..ab340d247 100644
--- a/python/vyos/ifconfig/register.py
+++ b/python/vyos/ifconfig/section.py
@@ -16,9 +16,10 @@
import netifaces
-class Register:
+class Section:
# the known interface prefixes
_prefixes = {}
+ _classes = []
# class need to define: definition['prefixes']
# the interface prefixes declared by a class used to name interface with
@@ -26,9 +27,16 @@ class Register:
@classmethod
def register(cls, klass):
+ """
+ A function to use as decorator the interfaces classes
+ It register the prefix for the interface (eth, dum, vxlan, ...)
+ with the class which can handle it (EthernetIf, DummyIf,VXLANIf, ...)
+ """
if not klass.definition.get('prefixes',[]):
raise RuntimeError(f'valid interface prefixes not defined for {klass.__name__}')
+ cls._classes.append(klass)
+
for ifprefix in klass.definition['prefixes']:
if ifprefix in cls._prefixes:
raise RuntimeError(f'only one class can be registered for prefix "{ifprefix}" type')
@@ -38,7 +46,11 @@ class Register:
@classmethod
def _basename (cls, name, vlan):
- # remove number from interface name
+ """
+ remove the number at the end of interface name
+ name: name of the interface
+ vlan: if vlan is True, do not stop at the vlan number
+ """
name = name.rstrip('0123456789')
name = name.rstrip('.')
if vlan:
@@ -47,15 +59,13 @@ class Register:
@classmethod
def section(cls, name, vlan=True):
- # return the name of a section an interface should be under
+ """
+ return the name of a section an interface should be under
+ name: name of the interface (eth0, dum1, ...)
+ vlan: should we try try to remove the VLAN from the number
+ """
name = cls._basename(name, vlan)
- # XXX: To leave as long as vti and input are not moved to vyos
- if name == 'vti':
- return 'vti'
- if name == 'ifb':
- return 'input'
-
if name in cls._prefixes:
return cls._prefixes[name].definition['section']
return ''
@@ -68,15 +78,13 @@ class Register:
raise ValueError(f'No type found for interface name: {name}')
@classmethod
- def _listing (cls,section=''):
+ def _intf_under_section (cls,section=''):
+ """
+ return a generator with the name of the interface which are under a section
+ """
interfaces = netifaces.interfaces()
for ifname in interfaces:
- # XXX: Temporary hack as vti and input are not yet moved from vyatta to vyos
- if ifname.startswith('vti') or ifname.startswith('input'):
- yield ifname
- continue
-
ifsection = cls.section(ifname)
if not ifsection:
continue
@@ -87,9 +95,37 @@ class Register:
yield ifname
@classmethod
- def listing(cls, section=''):
- return list(cls._listing(section))
+ def interfaces(cls, section=''):
+ """
+ return a list of the name of the interface which are under a section
+ if no section is provided, then it returns all configured interfaces
+ """
+ return list(cls._intf_under_section(section))
+ @classmethod
+ def _intf_with_feature(cls, feature=''):
+ """
+ return a generator with the name of the interface which have
+ a particular feature set in their definition such as:
+ bondable, broadcast, bridgeable, ...
+ """
+ for klass in cls._classes:
+ if klass.definition[feature]:
+ yield klass.definition['section']
-# XXX: TODO - limit name for VRF interfaces
+ @classmethod
+ def feature(cls, feature=''):
+ """
+ return list with the name of the interface which have
+ a particular feature set in their definition such as:
+ bondable, broadcast, bridgeable, ...
+ """
+ return list(cls._intf_with_feature(feature))
+ @classmethod
+ def reserved(cls):
+ """
+ return list with the interface name prefixes
+ eth, lo, vxlan, dum, ...
+ """
+ return list(cls._prefixes.keys())
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 05060669a..009a53a82 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -143,7 +143,7 @@ class GREIf(_Tunnel):
options = ['local', 'remote', 'ttl', 'tos', 'key']
updates = ['local', 'remote', 'ttl', 'tos',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
create = 'ip tunnel add {ifname} mode {type}'
change = 'ip tunnel cha {ifname}'
@@ -167,7 +167,7 @@ class GRETapIf(_Tunnel):
required = ['local', ]
options = ['local', 'remote', ]
- updates = []
+ updates = ['mtu', ]
create = 'ip link add {ifname} type {type}'
change = ''
@@ -193,7 +193,7 @@ class IP6GREIf(_Tunnel):
'hoplimit', 'tclass', 'flowlabel']
updates = ['local', 'remote', 'encaplimit',
'hoplimit', 'tclass', 'flowlabel',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
create = 'ip tunnel add {ifname} mode {type}'
change = 'ip tunnel cha {ifname} mode {type}'
@@ -227,7 +227,7 @@ class IPIPIf(_Tunnel):
options = ['local', 'remote', 'ttl', 'tos', 'key']
updates = ['local', 'remote', 'ttl', 'tos',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
create = 'ip tunnel add {ifname} mode {type}'
change = 'ip tunnel cha {ifname}'
@@ -252,7 +252,7 @@ class IPIP6If(_Tunnel):
'hoplimit', 'tclass', 'flowlabel']
updates = ['local', 'remote', 'encaplimit',
'hoplimit', 'tclass', 'flowlabel',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
create = 'ip -6 tunnel add {ifname} mode {type}'
change = 'ip -6 tunnel cha {ifname}'
@@ -288,7 +288,7 @@ class SitIf(_Tunnel):
options = ['local', 'remote', 'ttl', 'tos', 'key']
updates = ['local', 'remote', 'ttl', 'tos',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
create = 'ip tunnel add {ifname} mode {type}'
change = 'ip tunnel cha {ifname}'
@@ -309,7 +309,7 @@ class Sit6RDIf(SitIf):
# TODO: check if key can really be used with 6RD
options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix']
updates = ['remote', 'ttl', 'tos',
- 'multicast', 'allmulticast']
+ 'mtu', 'multicast', 'allmulticast']
def _create(self):
# do not call _Tunnel.create, building fully here
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
new file mode 100644
index 000000000..56ebe01d1
--- /dev/null
+++ b/python/vyos/ifconfig/vti.py
@@ -0,0 +1,31 @@
+# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+@Interface.register
+class VTIIf(Interface):
+ default = {
+ 'type': 'vti',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'vti',
+ 'prefixes': ['vti', ],
+ },
+ }
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index f918461d1..1b4d3876e 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -18,7 +18,8 @@ import os
import re
import fileinput
-from vyos.util import cmd, DEVNULL
+from vyos.util import cmd
+from vyos.util import DEVNULL
def check_and_add_host_key(host_name):
@@ -31,10 +32,10 @@ def check_and_add_host_key(host_name):
mode = 0o600
os.mknod(known_hosts, 0o600)
- keyscan_cmd = 'ssh-keyscan -t rsa {} 2>/dev/null'.format(host_name)
+ keyscan_cmd = 'ssh-keyscan -t rsa {}'.format(host_name)
try:
- host_key = cmd(keyscan_cmd, shell=True, stderr=DEVNULL)
+ host_key = cmd(keyscan_cmd, stderr=DEVNULL)
except OSError:
sys.exit("Can not get RSA host key")
@@ -61,9 +62,9 @@ def check_and_add_host_key(host_name):
print("Host key has changed!")
print("If you trust the host key fingerprint below, continue.")
- fingerprint_cmd = 'ssh-keygen -lf /dev/stdin <<< "{}"'.format(host_key)
+ fingerprint_cmd = 'ssh-keygen -lf /dev/stdin'
try:
- fingerprint = cmd(fingerprint_cmd, shell=True, stderr=DEVNULL)
+ fingerprint = cmd(fingerprint_cmd, stderr=DEVNULL, input=host_key)
except OSError:
sys.exit("Can not get RSA host key fingerprint.")
@@ -125,7 +126,7 @@ def get_remote_config(remote_file):
# Try header first, and look for 'OK' or 'Moved' codes:
curl_cmd = 'curl {0} -q -I {1}'.format(redirect_opt, remote_file)
try:
- curl_output = cmd(curl_cmd, shell=True)
+ curl_output = cmd(curl_cmd)
except OSError:
sys.exit(1)
@@ -142,6 +143,6 @@ def get_remote_config(remote_file):
curl_cmd = 'curl {0} -# {1}'.format(redirect_opt, remote_file)
try:
- return cmd(curl_cmd, shell=True, stderr=None)
+ return cmd(curl_cmd, stderr=None)
except OSError:
return None
diff --git a/python/vyos/template.py b/python/vyos/template.py
index e559120c0..6c73ce753 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -22,10 +22,17 @@ from vyos.defaults import directories
# reuse the same Environment to improve performance
-_templates_env = Environment(loader=FileSystemLoader(directories['templates']))
-_templates_mem = {}
+_templates_env = {
+ False: Environment(loader=FileSystemLoader(directories['templates'])),
+ True: Environment(loader=FileSystemLoader(directories['templates']), trim_blocks=True),
+}
+_templates_mem = {
+ False: {},
+ True: {},
+}
-def render(destination, template, content):
+
+def render(destination, template, content, trim_blocks=False, formater=None):
"""
render a template from the template directory, it will raise on any errors
destination: the file where the rendered template must be saved
@@ -41,15 +48,18 @@ def render(destination, template, content):
# Setup a renderer for the given template
# This is cached and re-used for performance
- if template not in _templates_mem:
- _templates_mem[template] = _templates_env.get_template(template)
- template = _templates_mem[template]
+ if template not in _templates_mem[trim_blocks]:
+ _templates_mem[trim_blocks][template] = _templates_env[trim_blocks].get_template(template)
+ template = _templates_mem[trim_blocks][template]
# As we are opening the file with 'w', we are performing the rendering
# before calling open() to not accidentally erase the file if the
# templating fails
content = template.render(content)
+ if formater:
+ content = formater(content)
+
# Write client config file
with open(destination, 'w') as f:
f.write(content)
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 291ce64ea..49c47cd85 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -16,67 +16,121 @@
import os
import re
import sys
-from subprocess import Popen, PIPE, STDOUT, DEVNULL
+from subprocess import Popen
+from subprocess import PIPE
+from subprocess import STDOUT
+from subprocess import DEVNULL
-def debug(flag):
- # this is to force all new flags to be registered here so that
- # they can be documented:
- # - developer: the code will drop into PBD on un-handled exception
- # - ifconfig: prints command and sysfs access on stdout for interface
- if flag not in ['developer', 'ifconfig']:
- return ''
- return flag if os.path.isfile(f'/tmp/vyos.{flag}.debug') else ''
+from vyos import debug
+# There is many (too many) ways to run command with python
+# os.system, subprocess.Popen, subproces.{run,call,check_output}
+# which all have slighty different behaviour
-def debug_msg(message, section=''):
- if debug(section):
- print(f'DEBUG/{section:<6} {message}')
+def popen(command, flag='', shell=None, input=None, timeout=None, env=None,
+ stdout=PIPE, stderr=None, decode=None):
+ """
+ popen is a wrapper helper aound subprocess.Popen
+ with it default setting it will return a tuple (out, err)
+ out: the output of the program run
+ err: the error code returned by the program
+
+ it can be affected by the following flags:
+ shell: do not try to auto-detect if a shell is required
+ for example if a pipe (|) or redirection (>, >>) is used
+ input: data to sent to the child process via STDIN
+ the data should be bytes but string will be converted
+ timeout: time after which the command will be considered to have failed
+ env: mapping that defines the environment variables for the new process
+ stdout: define how the output of the program should be handled
+ - PIPE (default), sends stdout to the output
+ - DEVNULL, discard the output
+ stderr: define how the output of the program should be handled
+ - None (default), send/merge the data to/with stderr
+ - PIPE, popen will append it to output
+ - STDOUT, send the data to be merged with stdout
+ - DEVNULL, discard the output
+ decode: specify the expected text encoding (utf-8, ascii, ...)
+
+ usage:
+ to get both stdout, and stderr: popen('command', stdout=PIPE, stderr=STDOUT)
+ to discard stdout and get stderr: popen('command', stdout=DEVNUL, stderr=PIPE)
+ """
+
+ # log if the flag is set, otherwise log if command is set
+ if not debug.enabled(flag):
+ flag = 'command'
+
+ cmd_msg = f"cmd '{command}'"
+ debug.message(cmd_msg, flag)
-def popen(command, section='', shell=None, input=None, timeout=None, env=None,
- universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None):
- """ popen does not raise, returns the output and error code of command """
use_shell = shell
+ stdin = None
if shell is None:
- use_shell = True if ' ' in command else False
+ use_shell = False
+ if ' ' in command:
+ use_shell = True
+ if env:
+ use_shell = True
+ if input:
+ stdin = PIPE
+ input = input.encode() if type(input) is str else input
p = Popen(
command,
- stdout=stdout, stderr=stderr,
+ stdin=stdin, stdout=stdout, stderr=stderr,
env=env, shell=use_shell,
- universal_newlines=universal_newlines,
)
- tmp = p.communicate(input, timeout)[0].strip()
- debug_msg(f"cmd '{command}'", section)
- decoded = tmp.decode(decode) if decode else tmp.decode()
+ tmp = p.communicate(input, timeout)
+ out1 = b''
+ out2 = b''
+ if stdout == PIPE:
+ out1 = tmp[0]
+ if stderr == PIPE:
+ out2 += tmp[1]
+ decoded1 = out1.decode(decode) if decode else out1.decode()
+ decoded2 = out2.decode(decode) if decode else out2.decode()
+ decoded1 = decoded1.replace('\r\n', '\n').strip()
+ decoded2 = decoded2.replace('\r\n', '\n').strip()
+ nl = '\n' if decoded1 and decoded2 else ''
+ decoded = decoded1 + nl + decoded2
if decoded:
- debug_msg(f"returned:\n{decoded}", section)
+ ret_msg = f"returned:\n{decoded}"
+ debug.message(ret_msg, flag)
return decoded, p.returncode
-def run(command, section='', shell=None, input=None, timeout=None, env=None,
- universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None):
- """ does not raise exception on error, returns error code """
+def run(command, flag='', shell=None, input=None, timeout=None, env=None,
+ stdout=DEVNULL, stderr=None, decode=None):
+ """
+ A wrapper around vyos.util.popen, which discard the stdout and
+ will return the error code of a command
+ """
_, code = popen(
- command, section,
+ command, flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,
- universal_newlines=universal_newlines,
decode=decode,
)
return code
-def cmd(command, section='', shell=None, input=None, timeout=None, env=None,
- universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None,
+def cmd(command, flag='', shell=None, input=None, timeout=None, env=None,
+ stdout=PIPE, stderr=None, decode=None,
raising=None, message=''):
- """ does raise exception, returns output of command """
+ """
+ A wrapper around vyos.util.popen, which returns the stdout and
+ will raise the error code of a command
+
+ raising: specify which call should be used when raising (default is OSError)
+ the class should only require a string as parameter
+ """
decoded, code = popen(
- command, section,
+ command, flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,
- universal_newlines=universal_newlines,
decode=decode,
)
if code != 0:
@@ -92,15 +146,17 @@ def cmd(command, section='', shell=None, input=None, timeout=None, env=None,
return decoded
-def call(command, section='', shell=None, input=None, timeout=None, env=None,
- universal_newlines=None, stdout=PIPE, stderr=STDOUT, decode=None):
- """ does not raise exception on error, returns error code, print output """
+def call(command, flag='', shell=None, input=None, timeout=None, env=None,
+ stdout=PIPE, stderr=None, decode=None):
+ """
+ A wrapper around vyos.util.popen, which print the stdout and
+ will return the error code of a command
+ """
out, code = popen(
- command, section,
+ command, flag,
stdout=stdout, stderr=stderr,
input=input, timeout=timeout,
env=env, shell=shell,
- universal_newlines=universal_newlines,
decode=decode,
)
print(out)
@@ -124,6 +180,16 @@ def chown(path, user, group):
gid = getgrnam(group).gr_gid
os.chown(path, uid, gid)
+
+def chmod_600(path):
+ """ make file only read/writable by owner """
+ from stat import S_IRUSR, S_IWUSR
+
+ if os.path.exists(path):
+ bitmask = S_IRUSR | S_IWUSR
+ os.chmod(path, bitmask)
+
+
def chmod_750(path):
""" make file/directory only executable to user and group """
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP
@@ -133,8 +199,8 @@ def chmod_750(path):
os.chmod(path, bitmask)
-def chmod_x(path):
- """ make file executable """
+def chmod_755(path):
+ """ make file executable by all """
from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH
if os.path.exists(path):
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py
index 98b32797a..e27281433 100755
--- a/src/completion/list_interfaces.py
+++ b/src/completion/list_interfaces.py
@@ -2,7 +2,14 @@
import sys
import argparse
-from vyos.ifconfig import Interface
+from vyos.ifconfig import Section
+
+
+def matching(feature):
+ for section in Section.feature(feature):
+ for intf in Section.interfaces(section):
+ yield intf
+
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
@@ -13,46 +20,23 @@ group.add_argument("-bo", "--bondable", action="store_true", help="List all bond
args = parser.parse_args()
-# XXX: Need to be rewritten using the data in the class definition
-# XXX: It can be done once vti and input are moved into vyos
-# XXX: We store for each class what type they are (broadcast, bridgeabe, ...)
-
if args.type:
try:
- interfaces = Interface.listing(args.type)
-
+ interfaces = Section.interfaces(args.type)
+ print(" ".join(interfaces))
except ValueError as e:
print(e, file=sys.stderr)
print("")
elif args.broadcast:
- eth = Interface.listing("ethernet")
- bridge = Interface.listing("bridge")
- bond = Interface.listing("bonding")
- interfaces = eth + bridge + bond
+ print(" ".join(matching("broadcast")))
elif args.bridgeable:
- eth = Interface.listing("ethernet")
- bond = Interface.listing("bonding")
- l2tpv3 = Interface.listing("l2tpv3")
- openvpn = Interface.listing("openvpn")
- wireless = Interface.listing("wireless")
- tunnel = Interface.listing("tunnel")
- vxlan = Interface.listing("vxlan")
- geneve = Interface.listing("geneve")
-
- interfaces = eth + bond + l2tpv3 + openvpn + vxlan + tunnel + wireless + geneve
+ print(" ".join(matching("bridgeable")))
elif args.bondable:
- interfaces = []
- eth = Interface.listing("ethernet")
-
# we need to filter out VLAN interfaces identified by a dot (.) in their name
- for intf in eth:
- if not '.' in intf:
- interfaces.append(intf)
+ print(" ".join([intf for intf in matching("bondable") if '.' not in intf]))
else:
- interfaces = Interface.listing()
-
-print(" ".join(interfaces))
+ print(" ".join(Section.interfaces()))
diff --git a/src/completion/list_openvpn_clients.py b/src/completion/list_openvpn_clients.py
index 17b0c7008..177ac90c9 100755
--- a/src/completion/list_openvpn_clients.py
+++ b/src/completion/list_openvpn_clients.py
@@ -18,7 +18,7 @@ import os
import sys
import argparse
-from vyos.ifconfig import Interface
+from vyos.ifconfig import Section
def get_client_from_interface(interface):
clients = []
@@ -50,7 +50,7 @@ if __name__ == "__main__":
if args.interface:
clients = get_client_from_interface(args.interface)
elif args.all:
- for interface in Interface.listing("openvpn"):
+ for interface in Section.interfaces("openvpn"):
clients += get_client_from_interface(interface)
print(" ".join(clients))
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/bcast_relay.py b/src/conf_mode/bcast_relay.py
index 8d4c4a89a..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 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
- call('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
- call('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 c92d6a4e1..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 import ConfigError
+from vyos.template import render
from vyos.util import call
+from vyos import ConfigError
-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:
- call('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
- call('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 553247b88..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 call
+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
- call('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)
-
- call('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 9355d9794..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 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:
- call('sudo systemctl restart isc-dhcpv6-relay.service')
+ call('systemctl restart isc-dhcp-relay6.service')
else:
# DHCPv6 relay support is removed in the commit
- call('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 950ca1ce2..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 call
-
-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:
+ if not dhcpv6 or dhcpv6['disabled']:
return None
- if dhcpv6['disabled']:
- print('Warning: DHCPv6 server 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', '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
- call('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)
- call('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 4071c05c9..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 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
- call("systemctl stop pdns-recursor")
+ call("systemctl stop pdns-recursor.service")
if os.path.isfile(config_file):
os.unlink(config_file)
else:
- call("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 b54d76b06..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 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']:
- call('/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:
- call('/etc/init.d/ddclient restart')
+ call('systemctl restart ddclient.service')
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 7c2f79abc..dd5819f9f 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -173,7 +173,7 @@ def apply(config):
# restart pdns if it is used
ret = run('/usr/bin/rec_control ping')
if ret == 0:
- call('/etc/init.d/pdns-recursor restart >/dev/null')
+ call('systemctl restart pdns-recursor.service')
return None
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index da7193c9b..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 call
+from vyos.template import render
config_file = '/etc/nginx/sites-available/default'
@@ -133,18 +132,10 @@ 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
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index 77e2bb150..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 call
+from vyos.template import render
config_file = r'/etc/igmpproxy.conf'
@@ -116,16 +115,7 @@ 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):
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 8a615ec62..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'):
@@ -441,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):
@@ -535,10 +590,42 @@ 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" 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
if openvpn['server_reject_unconfigured']:
@@ -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 353a5a12c..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, 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': '',
@@ -161,11 +161,6 @@ def verify(pppoe):
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, trim_blocks=True)
-
# set up configuration file path variables where our templates will be
# rendered into
intf = pppoe['intf']
@@ -195,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
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 19538da72..c51048aeb 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -584,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-wireless.py b/src/conf_mode/interfaces-wireless.py
index 07c4537b4..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, 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(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(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(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'])
@@ -774,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']:
@@ -784,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
@@ -799,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 c44a993c4..da1855cd9 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -18,15 +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, chmod_x, 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': [],
@@ -141,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']
@@ -175,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
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index dc04e9131..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 call
+from vyos.template import render
+
ra_conn_name = "remote-access"
charon_conf_file = "/etc/strongswan.d/charon.conf"
@@ -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:
diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py
index 4b365a566..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
@@ -25,7 +23,6 @@ from vyos import ConfigError
from vyos.util import cmd
from vyos.util import call
-
vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode']
dependencies = [
@@ -86,17 +83,17 @@ def generate(cert):
# certbot will attempt to reload nginx, even with 'certonly';
# start nginx if not active
- ret = call('systemctl is-active --quiet nginx.ervice')
+ ret = call('systemctl is-active --quiet nginx.service')
if ret:
- call('sudo systemctl start nginx.service')
+ call('systemctl start nginx.service')
request_certbot(cert)
def apply(cert):
if cert is not None:
- call('sudo systemctl restart certbot.timer')
+ call('systemctl restart certbot.timer')
else:
- call('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 ec59c68d0..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 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,16 +220,9 @@ 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):
diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py
index 9230aaf61..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 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']:
- call('sudo systemctl stop mdns-repeater')
+ call('systemctl stop mdns-repeater.service')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- call('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 75328dfd7..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 import ConfigError
from vyos.util import call
-
+from vyos.template import render
+from vyos import ConfigError
config_file = r'/etc/ntp.conf'
@@ -100,16 +98,7 @@ 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):
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index cf4db5f54..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 call
+from vyos.template import render
config_file = r'/tmp/bfd.frr'
@@ -191,16 +190,7 @@ 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):
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index 141b1950d..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 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):
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index b5753aea8..0a241277d 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -16,12 +16,10 @@
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 call
+from vyos.template import render
config_file = r'/tmp/ldpd.frr'
@@ -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):
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 44fc9293b..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 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):
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index bfc3a707e..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 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:
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 75a324260..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 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):
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 4a69e8742..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 call
+from vyos.template import render
config_file_client = r'/etc/snmp/snmp.conf'
@@ -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
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index a6cdb7ccc..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 call
+from vyos.template import render
config_file = r'/etc/ssh/sshd_config'
@@ -120,15 +119,7 @@ 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):
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 43732cfae..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,10 +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
from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.template import render
+
radius_config_file = "/etc/pam_radius_auth.conf"
@@ -211,16 +212,16 @@ def generate(login):
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
@@ -256,7 +257,7 @@ def apply(login):
command += " {}".format(user['name'])
try:
- call(command)
+ cmd(command)
uid = getpwnam(user['name']).pw_uid
gid = getpwnam(user['name']).pw_gid
@@ -299,7 +300,7 @@ def apply(login):
call('pkill -HUP -u {}'.format(user))
# Remove user account but leave home directory to be safe
- call('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))
@@ -309,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
- os.system("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\' \
@@ -321,15 +324,18 @@ def apply(login):
-e \'/^group:[^#]*$/s/: */&mapname /\' \
/etc/nsswitch.conf"
- call(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
- os.system("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//\' \
@@ -337,10 +343,10 @@ def apply(login):
-e \'s/[ \t]*$//\' \
/etc/nsswitch.conf"
- call(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-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-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 7a7246783..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 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
- call('systemctl stop tftpd@{0..20}')
+ call('systemctl stop tftpd@{0..20}.service')
# bail out early - e.g. service deletion
if tftpd is 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 586424c09..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):
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index 3f1b73385..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 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
diff --git a/src/etc/init.d/isc-dhcpv4-server b/src/etc/init.d/isc-dhcpv4-server
deleted file mode 100755
index 94a1020ac..000000000
--- a/src/etc/init.d/isc-dhcpv4-server
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/bin/sh
-#
-#
-
-### BEGIN INIT INFO
-# Provides: isc-dhcpv4-server
-# Required-Start: $remote_fs $network $syslog
-# Required-Stop: $remote_fs $network $syslog
-# Should-Start: $local_fs slapd $named
-# Should-Stop: $local_fs slapd
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: IPv4 DHCP server
-# Description: Dynamic Host Configuration Protocol Server for IPv4
-### END INIT INFO
-
-PATH=/sbin:/bin:/usr/sbin:/usr/bin
-
-test -f /usr/sbin/dhcpd || exit 0
-
-DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcpv4-server}"
-
-# It is not safe to start if we don't have a default configuration...
-if [ ! -f "$DHCPD_DEFAULT" ]; then
- echo "$DHCPD_DEFAULT does not exist! - Aborting..."
- exit 0
-fi
-
-. /lib/lsb/init-functions
-
-# Read init script configuration
-[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT"
-
-NAME=dhcpd
-DESC="ISC DHCP server"
-# fallback to default config file
-DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpd.conf}
-# try to read pid file name from config file, with fallback to /var/run/dhcpd.pid
-if [ -z "$DHCPD_PID" ]; then
- DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1)
-fi
-DHCPD_PID="${DHCPD_PID:-/var/run/dhcpd.pid}"
-
-test_config()
-{
- if ! /usr/sbin/dhcpd -t $OPTIONS -q -cf "$DHCPD_CONF" > /dev/null 2>&1; then
- echo "dhcpd self-test failed. Please fix $DHCPD_CONF."
- echo "The error was: "
- /usr/sbin/dhcpd -t $OPTIONS -cf "$DHCPD_CONF"
- exit 1
- fi
- touch /var/lib/dhcp/dhcpd.leases
-}
-
-# single arg is -v for messages, -q for none
-check_status()
-{
- if [ ! -r "$DHCPD_PID" ]; then
- test "$1" != -v || echo "$NAME is not running."
- return 3
- fi
- if read pid < "$DHCPD_PID" && ps -p "$pid" > /dev/null 2>&1; then
- test "$1" != -v || echo "$NAME is running."
- return 0
- else
- test "$1" != -v || echo "$NAME is not running but $DHCPD_PID exists."
- return 1
- fi
-}
-
-case "$1" in
- start)
- test_config
- log_daemon_msg "Starting $DESC" "$NAME"
- start-stop-daemon --start --oknodo --quiet --pidfile "$DHCPD_PID" \
- --exec /usr/sbin/dhcpd -- \
- -q $OPTIONS -cf "$DHCPD_CONF" -pf "$DHCPD_PID" $INTERFACES
- sleep 2
-
- if check_status -q; then
- log_end_msg 0
- else
- log_failure_msg "check syslog for diagnostics."
- log_end_msg 1
- exit 1
- fi
- ;;
- stop)
- log_daemon_msg "Stopping $DESC" "$NAME"
- start-stop-daemon --stop --oknodo --quiet --pidfile "$DHCPD_PID"
- log_end_msg $?
- rm -f "$DHCPD_PID"
- ;;
- restart | force-reload)
- test_config
- $0 stop
- sleep 2
- $0 start
- if [ "$?" != "0" ]; then
- exit 1
- fi
- ;;
- status)
- echo -n "Status of $DESC: "
- check_status -v
- exit "$?"
- ;;
- *)
- echo "Usage: $0 {start|stop|restart|force-reload|status}"
- exit 1
-esac
-
-exit 0
diff --git a/src/etc/init.d/isc-dhcpv6-relay b/src/etc/init.d/isc-dhcpv6-relay
deleted file mode 100755
index e553eafd1..000000000
--- a/src/etc/init.d/isc-dhcpv6-relay
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/sh
-#
-#
-
-### BEGIN INIT INFO
-# Provides: isc-dhcpv6-relay
-# Required-Start: $remote_fs $network
-# Required-Stop: $remote_fs $network
-# Should-Start: $local_fs
-# Should-Stop: $local_fs
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: IPv6 DHCP relay
-# Description: Dynamic Host Configuration Protocol Relay for IPv6
-### END INIT INFO
-
-# It is not safe to start if we don't have a default configuration...
-if [ ! -f /etc/default/isc-dhcpv6-relay ]; then
- echo "/etc/default/isc-dhcpv6-relay does not exist! - Aborting..."
- exit 1
-fi
-
-# Source init functions
-. /lib/lsb/init-functions
-
-# Read init script configuration (interfaces the daemon should listen on
-# and the DHCP server we should forward requests to.)
-[ -f /etc/default/isc-dhcpv6-relay ] && . /etc/default/isc-dhcpv6-relay
-
-DHCRELAYPID=/var/run/dhcv6relay.pid
-
-case "$1" in
- start)
- start-stop-daemon --start --oknodo --quiet --pidfile $DHCRELAYPID \
- --exec /usr/sbin/dhcrelay -- -q $OPTIONS -pf $DHCRELAYPID
- ;;
- stop)
- start-stop-daemon --stop --oknodo --quiet --pidfile $DHCRELAYPID
- ;;
- restart | force-reload)
- $0 stop
- sleep 2
- $0 start
- ;;
- *)
- echo "Usage: /etc/init.d/isc-dhcpv6-relay {start|stop|restart|force-reload}"
- exit 1
-esac
-
-exit 0
diff --git a/src/etc/init.d/isc-dhcpv6-server b/src/etc/init.d/isc-dhcpv6-server
deleted file mode 100755
index f6b27cb4a..000000000
--- a/src/etc/init.d/isc-dhcpv6-server
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/bin/sh
-#
-#
-
-### BEGIN INIT INFO
-# Provides: isc-dhcpv6-server
-# Required-Start: $remote_fs $network $syslog
-# Required-Stop: $remote_fs $network $syslog
-# Should-Start: $local_fs slapd $named
-# Should-Stop: $local_fs slapd
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: IPv6 DHCP server
-# Description: Dynamic Host Configuration Protocol Server for IPv6
-### END INIT INFO
-
-PATH=/sbin:/bin:/usr/sbin:/usr/bin
-
-test -f /usr/sbin/dhcpd || exit 0
-
-DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcpv6-server}"
-
-# It is not safe to start if we don't have a default configuration...
-if [ ! -f "$DHCPD_DEFAULT" ]; then
- echo "$DHCPD_DEFAULT does not exist! - Aborting..."
- exit 0
-fi
-
-. /lib/lsb/init-functions
-
-# Read init script configuration
-[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT"
-
-NAME=dhcpdv6
-DESC="ISC DHCP server IPv6"
-# fallback to default config file
-DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpdv6.conf}
-# try to read pid file name from config file, with fallback to /var/run/dhcpdv6.pid
-if [ -z "$DHCPD_PID" ]; then
- DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1)
-fi
-DHCPD_PID="${DHCPD_PID:-/var/run/dhcpdv6.pid}"
-
-test_config()
-{
- if ! /usr/sbin/dhcpd -t $OPTIONS -q -cf "$DHCPD_CONF" > /dev/null 2>&1; then
- echo "dhcpd self-test failed. Please fix $DHCPD_CONF."
- echo "The error was: "
- /usr/sbin/dhcpd -t $OPTIONS -cf "$DHCPD_CONF"
- exit 1
- fi
- touch /var/lib/dhcp/dhcpdv6.leases
-}
-
-# single arg is -v for messages, -q for none
-check_status()
-{
- if [ ! -r "$DHCPD_PID" ]; then
- test "$1" != -v || echo "$NAME is not running."
- return 3
- fi
- if read pid < "$DHCPD_PID" && ps -p "$pid" > /dev/null 2>&1; then
- test "$1" != -v || echo "$NAME is running."
- return 0
- else
- test "$1" != -v || echo "$NAME is not running but $DHCPD_PID exists."
- return 1
- fi
-}
-
-case "$1" in
- start)
- test_config
- log_daemon_msg "Starting $DESC" "$NAME"
- start-stop-daemon --start --oknodo --quiet --pidfile "$DHCPD_PID" \
- --exec /usr/sbin/dhcpd -- \
- -q $OPTIONS -cf "$DHCPD_CONF" -pf "$DHCPD_PID" $INTERFACES
- sleep 2
-
- if check_status -q; then
- log_end_msg 0
- else
- log_failure_msg "check syslog for diagnostics."
- log_end_msg 1
- exit 1
- fi
- ;;
- stop)
- log_daemon_msg "Stopping $DESC" "$NAME"
- start-stop-daemon --stop --oknodo --quiet --pidfile "$DHCPD_PID"
- log_end_msg $?
- rm -f "$DHCPD_PID"
- ;;
- restart | force-reload)
- test_config
- $0 stop
- sleep 2
- $0 start
- if [ "$?" != "0" ]; then
- exit 1
- fi
- ;;
- status)
- echo -n "Status of $DESC: "
- check_status -v
- exit "$?"
- ;;
- *)
- echo "Usage: $0 {start|stop|restart|force-reload|status}"
- exit 1
-esac
-
-exit 0
diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf
new file mode 100644
index 000000000..bb8e81d7a
--- /dev/null
+++ b/src/etc/systemd/system/hostapd@.service.d/override.conf
@@ -0,0 +1,10 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/hostapd
+EnvironmentFile=
+ExecStart=
+ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf
+PIDFile=/run/hostapd/%i.pid
diff --git a/src/etc/systemd/system/openvpn@.service.d/override.conf b/src/etc/systemd/system/openvpn@.service.d/override.conf
new file mode 100644
index 000000000..7946484a3
--- /dev/null
+++ b/src/etc/systemd/system/openvpn@.service.d/override.conf
@@ -0,0 +1,9 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=
+WorkingDirectory=/run/openvpn
+ExecStart=
+ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid
diff --git a/src/etc/systemd/system/pdns-recursor.service.d/override.conf b/src/etc/systemd/system/pdns-recursor.service.d/override.conf
new file mode 100644
index 000000000..602d7b774
--- /dev/null
+++ b/src/etc/systemd/system/pdns-recursor.service.d/override.conf
@@ -0,0 +1,5 @@
+[Service]
+WorkingDirectory=
+WorkingDirectory=/run/powerdns
+ExecStart=
+ExecStart=/usr/sbin/pdns_recursor --daemon=no --write-pid=no --disable-syslog --log-timestamp=no --config-dir=/run/powerdns
diff --git a/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf
new file mode 100644
index 000000000..20b25b726
--- /dev/null
+++ b/src/etc/systemd/system/wpa_supplicant@.service.d/override.conf
@@ -0,0 +1,10 @@
+[Unit]
+After=
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=
+WorkingDirectory=/run/wpa_supplicant
+EnvironmentFile=
+ExecStart=
+ExecStart=/sbin/wpa_supplicant -c%I.conf -Dnl80211,wext -i%I
diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3
new file mode 100755
index 000000000..bd0839e03
--- /dev/null
+++ b/src/migration-scripts/l2tp/2-to-3
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# - remove primary/secondary identifier from nameserver
+# - TODO: remove radius server req-limit
+
+import os
+import sys
+
+from sys import argv, exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'l2tp', 'remote-access']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # Migrate IPv4 DNS servers
+ dns_base = base + ['dns-servers']
+ if config.exists(dns_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(dns_base + [server]):
+ dns = config.return_value(dns_base + [server])
+ config.set(base + ['name-server'], value=dns, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv6 DNS servers
+ dns_base = base + ['dnsv6-servers']
+ if config.exists(dns_base):
+ for server in config.return_values(dns_base):
+ config.set(base + ['name-server'], value=server, replace=False)
+
+ config.delete(dns_base)
+
+ # Migrate IPv4 WINS servers
+ wins_base = base + ['wins-servers']
+ if config.exists(wins_base):
+ for server in ['server-1', 'server-2']:
+ if config.exists(wins_base + [server]):
+ wins = config.return_value(wins_base + [server])
+ config.set(base + ['wins-server'], value=wins, replace=False)
+
+ config.delete(wins_base)
+
+
+ # Remove RADIUS server req-limit node
+ radius_base = base + ['authentication', 'radius']
+ if config.exists(radius_base):
+ for server in config.list_nodes(radius_base + ['server']):
+ if config.exists(radius_base + ['server', server, 'req-limit']):
+ config.delete(radius_base + ['server', server, 'req-limit'])
+
+ # Migrate IPv6 prefixes
+ ipv6_base = base + ['client-ipv6-pool']
+ if config.exists(ipv6_base + ['prefix']):
+ prefix_old = config.return_values(ipv6_base + ['prefix'])
+ # delete old prefix CLI nodes
+ config.delete(ipv6_base + ['prefix'])
+ # create ned prefix tag node
+ config.set(ipv6_base + ['prefix'])
+ config.set_tag(ipv6_base + ['prefix'])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask)
+
+ if config.exists(ipv6_base + ['delegate-prefix']):
+ prefix_old = config.return_values(ipv6_base + ['delegate-prefix'])
+ # delete old delegate prefix CLI nodes
+ config.delete(ipv6_base + ['delegate-prefix'])
+ # create ned delegation tag node
+ config.set(ipv6_base + ['delegate '])
+ config.set_tag(ipv6_base + ['delegate '])
+
+ for p in prefix_old:
+ prefix = p.split(',')[0]
+ mask = p.split(',')[1]
+ config.set(ipv6_base + ['delegate', prefix, 'mask'], value=mask)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/dns_forwarding_restart.sh b/src/op_mode/dns_forwarding_restart.sh
index 8e556f2f0..64cc92115 100755
--- a/src/op_mode/dns_forwarding_restart.sh
+++ b/src/op_mode/dns_forwarding_restart.sh
@@ -2,7 +2,7 @@
if cli-shell-api existsEffective service dns forwarding; then
echo "Restarting the DNS forwarding service"
- systemctl restart pdns-recursor
+ systemctl restart pdns-recursor.service
else
echo "DNS forwarding is not configured"
fi
diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dynamic_dns.py
index 405dd9f04..e4e5043d5 100755
--- a/src/op_mode/dynamic_dns.py
+++ b/src/op_mode/dynamic_dns.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -23,8 +23,7 @@ import time
from vyos.config import Config
from vyos.util import call
-
-cache_file = r'/var/cache/ddclient/ddclient.cache'
+cache_file = r'/run/ddclient/ddclient.cache'
OUT_TMPL_SRC = """
{%- for entry in hosts -%}
@@ -86,9 +85,9 @@ def show_status():
def update_ddns():
- call('systemctl stop ddclient')
+ call('systemctl stop ddclient.service')
os.remove(cache_file)
- call('systemctl start ddclient')
+ call('systemctl start ddclient.service')
def main():
diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py
index 7f3ad7476..bf8c39fd6 100755
--- a/src/op_mode/flow_accounting_op.py
+++ b/src/op_mode/flow_accounting_op.py
@@ -70,13 +70,13 @@ def _is_host(host):
# check if flow-accounting running
def _uacctd_running():
- command = '/usr/bin/sudo /bin/systemctl status uacctd > /dev/null'
+ command = 'systemctl status uacctd.service > /dev/null'
return run(command) == 0
# get list of interfaces
def _get_ifaces_dict():
# run command to get ifaces list
- out = cmd('/bin/ip link show', universal_newlines=True)
+ out = cmd('/bin/ip link show')
# read output
ifaces_out = out.splitlines()
@@ -95,7 +95,6 @@ def _get_ifaces_dict():
def _get_flows_list():
# run command to get flows list
out = cmd(f'/usr/bin/pmacct -s -O json -T flows -p {uacctd_pipefile}',
- universal_newlines=True,
message='Failed to get flows list')
# read output
@@ -196,7 +195,7 @@ if not _uacctd_running():
# restart pmacct daemon
if cmd_args.action == 'restart':
# run command to restart flow-accounting
- cmd('/usr/bin/sudo /bin/systemctl restart uacctd',
+ cmd('systemctl restart uacctd.service',
message='Failed to restart flow-accounting')
# clear in-memory collected flows
diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py
index f65d383c0..cbc9ef973 100755
--- a/src/op_mode/generate_ssh_server_key.py
+++ b/src/op_mode/generate_ssh_server_key.py
@@ -14,14 +14,13 @@
# 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
-
+from sys import exit
from vyos.util import ask_yes_no
from vyos.util import cmd
if not ask_yes_no('Do you really want to remove the existing SSH host keys?'):
- sys.exit(0)
+ exit(0)
-cmd('sudo rm -v /etc/ssh/ssh_host_*')
-cmd('sudo dpkg-reconfigure openssh-server')
-cmd('sudo systemctl restart ssh')
+cmd('rm -v /etc/ssh/ssh_host_*')
+cmd('dpkg-reconfigure openssh-server')
+cmd('systemctl restart ssh.service')
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 0f3619411..4ab91384b 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -24,6 +24,7 @@ from vyos.util import ask_yes_no
from vyos.util import cmd
from vyos.util import call
from vyos.util import run
+from vyos.util import STDOUT
systemd_sched_file = "/run/systemd/shutdown/scheduled"
@@ -97,14 +98,14 @@ def execute_shutdown(time, reboot = True, ask=True):
chk_vyatta_based_reboots()
###
- out = cmd(f'/sbin/shutdown {action} now')
+ out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT)
print(out.split(",",1)[0])
return
elif len(time) == 1:
# Assume the argument is just time
ts = parse_time(time[0])
if ts:
- cmd(f'/sbin/shutdown {action} {time[0]}')
+ cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT)
else:
sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
elif len(time) == 2:
@@ -115,7 +116,7 @@ def execute_shutdown(time, reboot = True, ask=True):
t = datetime.combine(ds, ts)
td = t - datetime.now()
t2 = 1 + int(td.total_seconds())//60 # Get total minutes
- cmd('/sbin/shutdown {action} {t2}')
+ cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT)
else:
if not ts:
sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
index 618cad5ea..dbd3eb4d1 100755
--- a/src/op_mode/reset_openvpn.py
+++ b/src/op_mode/reset_openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -14,57 +14,18 @@
# 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
-
-from time import sleep
-from netifaces import interfaces
-from vyos.util import process_running, cmd
-
-def get_config_name(intf):
- cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
- return cfg_file
-
-def get_pid_file(intf):
- pid_file = r'/var/run/openvpn/{}.pid'.format(intf)
- return pid_file
-
+from sys import argv, exit
+from vyos.util import call
if __name__ == '__main__':
- if (len(sys.argv) < 1):
- print("Must specify OpenVPN interface name!")
- sys.exit(1)
-
- interface = sys.argv[1]
- if os.path.isfile(get_config_name(interface)):
- pidfile = '/var/run/openvpn/{}.pid'.format(interface)
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop'
- command += ' --oknodo'
- command += ' --quiet'
- command += ' --pidfile ' + pidfile
- cmd(command)
-
- # When stopping OpenVPN 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 interface in interfaces():
- sleep(0.250) # 250ms
-
- # re-start OpenVPN process
- command = 'start-stop-daemon'
- command += ' --start'
- command += ' --oknodo'
- command += ' --quiet'
- command += ' --pidfile ' + get_pid_file(interface)
- command += ' --exec /usr/sbin/openvpn'
- # now pass arguments to openvpn binary
- command += ' --'
- command += ' --daemon openvpn-' + interface
- command += ' --config ' + get_config_name(interface)
+ if (len(argv) < 1):
+ print('Must specify OpenVPN interface name!')
+ exit(1)
- cmd(command)
+ interface = argv[1]
+ if os.path.isfile(f'/run/openvpn/{interface}.conf'):
+ call(f'systemctl restart openvpn@{interface}.service')
else:
- print("OpenVPN interface {} does not exist!".format(interface))
- sys.exit(1)
+ print(f'OpenVPN interface "{interface}" does not exist!')
+ exit(1)
diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py
index 66dc435b3..af4fb2d15 100755
--- a/src/op_mode/restart_dhcp_relay.py
+++ b/src/op_mode/restart_dhcp_relay.py
@@ -39,7 +39,7 @@ if __name__ == '__main__':
if not c.exists_effective('service dhcp-relay'):
print("DHCP relay service not configured")
else:
- call('sudo systemctl restart isc-dhcp-relay.service')
+ call('systemctl restart isc-dhcp-server.service')
sys.exit(0)
elif args.ipv6:
@@ -47,7 +47,7 @@ if __name__ == '__main__':
if not c.exists_effective('service dhcpv6-relay'):
print("DHCPv6 relay service not configured")
else:
- call('sudo systemctl restart isc-dhcpv6-relay.service')
+ call('systemctl restart isc-dhcp-server6.service')
sys.exit(0)
else:
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index a79033f69..c49e604b7 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -193,7 +193,7 @@ if __name__ == '__main__':
sys.exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcpv4-server.service') != 0:
+ if call('systemctl -q is-active isc-dhcp-server.service') != 0:
print("WARNING: DHCP server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
index 18baa5517..d686defc0 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -179,7 +179,7 @@ if __name__ == '__main__':
sys.exit(0)
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
- if call('systemctl -q is-active isc-dhcpv6-server.service') != 0:
+ if call('systemctl -q is-active isc-dhcp-server6.service') != 0:
print("WARNING: DHCPv6 server is configured but not started. Data may be stale.")
if args.leases:
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index fe6ecbae5..8599c958f 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -33,6 +33,7 @@ import vyos.limericks
from vyos.util import cmd
from vyos.util import call
from vyos.util import run
+from vyos.util import DEVNULL
parser = argparse.ArgumentParser()
@@ -82,7 +83,7 @@ if __name__ == '__main__':
# Get hypervisor name, if any
system_type = "bare metal"
try:
- hypervisor = cmd('hvinfo 2>/dev/null')
+ hypervisor = cmd('hvinfo',stderr=DEVNULL)
system_type = "{0} guest".format(hypervisor)
except OSError:
# hvinfo returns 1 if it cannot detect any hypervisor
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index 2778deaab..7e2076820 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -87,7 +87,7 @@ class KeepalivedFifo:
def _run_command(self, command):
logger.debug("Running the command: {}".format(command))
try:
- cmd(command, universal_newlines=True)
+ cmd(command)
except OSError as err:
logger.error(f'Unable to execute command "{command}": {err}')
diff --git a/src/systemd/accel-ppp@.service b/src/systemd/accel-ppp@.service
new file mode 100644
index 000000000..256112769
--- /dev/null
+++ b/src/systemd/accel-ppp@.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Accel-PPP - High performance VPN server application for Linux
+RequiresMountsFor=/run
+ConditionPathExists=/run/accel-pppd/%i.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/accel-pppd
+ExecStart=/usr/sbin/accel-pppd -d -p /run/accel-pppd/%i.pid -c /run/accel-pppd/%i.conf
+ExecReload=/bin/kill -SIGUSR1 $MAINPID
+PIDFile=/run/accel-pppd/%i.pid
+Type=forking
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/ddclient.service b/src/systemd/ddclient.service
new file mode 100644
index 000000000..a4d55827a
--- /dev/null
+++ b/src/systemd/ddclient.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Dynamic DNS Update Client
+RequiresMountsFor=/run
+ConditionPathExists=/run/ddclient/ddclient.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/ddclient
+Type=forking
+PIDFile=/run/ddclient/ddclient.pid
+ExecStart=/usr/sbin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/isc-dhcp-relay.service b/src/systemd/isc-dhcp-relay.service
new file mode 100644
index 000000000..ebf4d234e
--- /dev/null
+++ b/src/systemd/isc-dhcp-relay.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=ISC DHCP IPv4 relay
+Documentation=man:dhcrelay(8)
+Wants=network-online.target
+ConditionPathExists=/run/dhcp-relay/dhcp.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/dhcp-relay
+EnvironmentFile=/run/dhcp-relay/dhcp.conf
+ExecStart=/usr/sbin/dhcrelay -d -4 $OPTIONS
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service
new file mode 100644
index 000000000..a477618b1
--- /dev/null
+++ b/src/systemd/isc-dhcp-relay6.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=ISC DHCP IPv6 relay
+Documentation=man:dhcrelay(8)
+Wants=network-online.target
+ConditionPathExists=/run/dhcp-relay/dhcpv6.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/dhcp-relay
+EnvironmentFile=/run/dhcp-relay/dhcpv6.conf
+ExecStart=/usr/sbin/dhcrelay -d -6 $OPTIONS
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/isc-dhcp-server.service b/src/systemd/isc-dhcp-server.service
new file mode 100644
index 000000000..d848e3df1
--- /dev/null
+++ b/src/systemd/isc-dhcp-server.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=ISC DHCP IPv4 server
+Documentation=man:dhcpd(8)
+RequiresMountsFor=/run
+ConditionPathExists=/run/dhcp-server/dhcpd.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/dhcp-server
+# The leases files need to be root:vyattacfg even when dropping privileges
+ExecStart=/bin/sh -ec '\
+ CONFIG_FILE=/run/dhcp-server/dhcpd.conf; \
+ [ -e /config/dhcpd.leases ] || touch /config/dhcpd.leases; \
+ chown root:vyattacfg /config/dhcpd.leases; \
+ chmod 664 /config/dhcpd.leases; \
+ exec /usr/sbin/dhcpd -user nobody -group nogroup -f -4 -pf /run/dhcp-server/dhcpd.pid -cf $CONFIG_FILE -lf /config/dhcpd.leases'
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/systemd/isc-dhcp-server6.service b/src/systemd/isc-dhcp-server6.service
new file mode 100644
index 000000000..743f16840
--- /dev/null
+++ b/src/systemd/isc-dhcp-server6.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=ISC DHCP IPv6 server
+Documentation=man:dhcpd(8)
+RequiresMountsFor=/run
+ConditionPathExists=/run/dhcp-server/dhcpd.conf
+After=vyos-router.service
+
+[Service]
+WorkingDirectory=/run/dhcp-server
+# The leases files need to be root:vyattacfg even when dropping privileges
+ExecStart=/bin/sh -ec '\
+ [ -e /config/dhcpdv6.leases ] || touch /config/dhcpdv6.leases; \
+ chown root:vyattacfg /config/dhcpdv6.leases; \
+ chmod 664 /config/dhcpdv6.leases; \
+ exec /usr/sbin/dhcpd -user nobody -group nogroup -f -6 -pf /run/dhcp-server/dhcpdv6.pid -cf /run/dhcp-server/dhcpdv6.conf -lf /config/dhcpdv6.leases'
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/etc/systemd/system/ppp@.service b/src/systemd/ppp@.service
index d271efb41..bb4622034 100644
--- a/src/etc/systemd/system/ppp@.service
+++ b/src/systemd/ppp@.service
@@ -1,6 +1,6 @@
[Unit]
Description=Dialing PPP connection %I
-After=network.target
+After=vyos-router.service
[Service]
ExecStart=/usr/sbin/pppd call %I nodetach nolog
diff --git a/src/systemd/tftpd@.service b/src/systemd/tftpd@.service
index e5c289466..266bc0962 100644
--- a/src/systemd/tftpd@.service
+++ b/src/systemd/tftpd@.service
@@ -1,6 +1,6 @@
[Unit]
Description=TFTP server
-After=network.target
+After=vyos-router.service
RequiresMountsFor=/run
[Service]