summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dhcp-client/ipv4.tmpl17
-rw-r--r--data/templates/dhcp-client/ipv6.tmpl4
-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/https/nginx.default.tmpl1
-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.tmpl186
-rw-r--r--data/templates/pppoe/ip-down.script.tmpl6
-rw-r--r--data/templates/pppoe/ip-up.script.tmpl12
-rw-r--r--data/templates/pppoe/peer.tmpl9
-rw-r--r--data/templates/sstp/sstp.config.tmpl3
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl7
-rw-r--r--debian/control1
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/include/interface-hw-id.xml.i12
-rw-r--r--interface-definitions/include/port-number.xml.i12
-rw-r--r--interface-definitions/include/radius-server.xml.i2
-rw-r--r--interface-definitions/include/vif-s.xml.i1
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in13
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in54
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in4
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in2
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in16
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in36
-rw-r--r--interface-definitions/interfaces-wireless.xml.in13
-rw-r--r--interface-definitions/vpn-l2tp.xml.in (renamed from interface-definitions/l2tp-server.xml.in)176
-rw-r--r--op-mode-definitions/ipoe-server.xml38
-rw-r--r--op-mode-definitions/reset-vpn.xml16
-rw-r--r--op-mode-definitions/show-raid.xml2
-rw-r--r--python/vyos/__init__.py2
-rw-r--r--python/vyos/airbag.py168
-rw-r--r--python/vyos/authutils.py2
-rw-r--r--python/vyos/debug.py182
-rw-r--r--python/vyos/defaults.py1
-rw-r--r--python/vyos/dicts.py50
-rw-r--r--python/vyos/ifconfig/__init__.py1
-rw-r--r--python/vyos/ifconfig/bond.py3
-rw-r--r--python/vyos/ifconfig/bridge.py3
-rw-r--r--python/vyos/ifconfig/control.py13
-rw-r--r--python/vyos/ifconfig/dhcp.py194
-rw-r--r--python/vyos/ifconfig/ethernet.py2
-rw-r--r--python/vyos/ifconfig/input.py31
-rw-r--r--python/vyos/ifconfig/interface.py46
-rw-r--r--python/vyos/ifconfig/macvlan.py11
-rw-r--r--python/vyos/ifconfig/section.py (renamed from python/vyos/ifconfig/register.py)72
-rw-r--r--python/vyos/ifconfig/stp.py2
-rw-r--r--python/vyos/ifconfig/tunnel.py16
-rw-r--r--python/vyos/ifconfig/vti.py31
-rw-r--r--python/vyos/ifconfig/vxlan.py25
-rw-r--r--python/vyos/ifconfig_vlan.py22
-rw-r--r--python/vyos/ioctl.py8
-rw-r--r--python/vyos/remote.py15
-rw-r--r--python/vyos/template.py65
-rw-r--r--python/vyos/util.py158
-rw-r--r--python/vyos/version.py15
-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/arp.py6
-rwxr-xr-xsrc/conf_mode/bcast_relay.py19
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py32
-rwxr-xr-xsrc/conf_mode/dhcp_server.py56
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py29
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py54
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py23
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py46
-rwxr-xr-xsrc/conf_mode/firewall_options.py26
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py31
-rwxr-xr-xsrc/conf_mode/host_name.py16
-rwxr-xr-xsrc/conf_mode/http-api.py7
-rwxr-xr-xsrc/conf_mode/https.py19
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py20
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py27
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py22
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py22
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py5
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py352
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py58
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py52
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py139
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py26
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py412
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py156
-rwxr-xr-xsrc/conf_mode/interfaces-wirelessmodem.py54
-rwxr-xr-xsrc/conf_mode/ipsec-settings.py39
-rwxr-xr-xsrc/conf_mode/le_cert.py16
-rwxr-xr-xsrc/conf_mode/lldp.py25
-rwxr-xr-xsrc/conf_mode/mdns_repeater.py21
-rwxr-xr-xsrc/conf_mode/ntp.py21
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py18
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py18
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py21
-rwxr-xr-xsrc/conf_mode/protocols_pim.py18
-rwxr-xr-xsrc/conf_mode/salt-minion.py19
-rwxr-xr-xsrc/conf_mode/service-ipoe.py20
-rwxr-xr-xsrc/conf_mode/service-pppoe.py19
-rwxr-xr-xsrc/conf_mode/service-router-advert.py19
-rwxr-xr-xsrc/conf_mode/snmp.py49
-rwxr-xr-xsrc/conf_mode/ssh.py19
-rwxr-xr-xsrc/conf_mode/system-ip.py4
-rwxr-xr-xsrc/conf_mode/system-ipv6.py4
-rwxr-xr-xsrc/conf_mode/system-login.py51
-rwxr-xr-xsrc/conf_mode/system-options.py4
-rwxr-xr-xsrc/conf_mode/system-syslog.py25
-rwxr-xr-xsrc/conf_mode/system-timezone.py4
-rwxr-xr-xsrc/conf_mode/system-wifi-regdom.py21
-rwxr-xr-xsrc/conf_mode/tftp_server.py19
-rwxr-xr-xsrc/conf_mode/vpn-pptp.py21
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py386
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py130
-rwxr-xr-xsrc/conf_mode/vrf.py16
-rwxr-xr-xsrc/conf_mode/vrrp.py30
-rw-r--r--src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook7
-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/helpers/validate-value.py4
-rwxr-xr-xsrc/helpers/vyos-merge-config.py6
-rwxr-xr-xsrc/migration-scripts/interfaces/7-to-83
-rwxr-xr-xsrc/migration-scripts/interfaces/8-to-952
-rwxr-xr-xsrc/migration-scripts/l2tp/2-to-3111
-rwxr-xr-xsrc/op_mode/connect_disconnect.py6
-rwxr-xr-xsrc/op_mode/dns_forwarding_reset.py23
-rwxr-xr-xsrc/op_mode/dns_forwarding_restart.sh2
-rwxr-xr-xsrc/op_mode/dynamic_dns.py11
-rwxr-xr-xsrc/op_mode/flow_accounting_op.py7
-rwxr-xr-xsrc/op_mode/format_disk.py6
-rwxr-xr-xsrc/op_mode/generate_ssh_server_key.py11
-rwxr-xr-xsrc/op_mode/lldp_op.py6
-rwxr-xr-xsrc/op_mode/powerctrl.py13
-rwxr-xr-xsrc/op_mode/reset_openvpn.py61
-rwxr-xr-xsrc/op_mode/reset_vpn.py70
-rwxr-xr-xsrc/op_mode/restart_dhcp_relay.py6
-rwxr-xr-xsrc/op_mode/restart_frr.py10
-rwxr-xr-xsrc/op_mode/show_acceleration.py13
-rwxr-xr-xsrc/op_mode/show_dhcp.py4
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py4
-rwxr-xr-xsrc/op_mode/snmp.py4
-rwxr-xr-xsrc/op_mode/version.py9
-rwxr-xr-xsrc/op_mode/wireguard.py11
-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
157 files changed, 3147 insertions, 2909 deletions
diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl
new file mode 100644
index 000000000..43f273077
--- /dev/null
+++ b/data/templates/dhcp-client/ipv4.tmpl
@@ -0,0 +1,17 @@
+# generated by ifconfig.py
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+timeout 60;
+retry 300;
+
+interface "{{ ifname }}" {
+ send host-name "{{ hostname }}";
+ {% if client_id -%}
+ send dhcp-client-identifier "{{ client_id }}";
+ {% endif -%}
+ {% if vendor_class_id -%}
+ send vendor-class-identifier "{{ vendor_class_id }}";
+ {% endif -%}
+ request subnet-mask, broadcast-address, routers, domain-name-servers,
+ rfc3442-classless-static-routes, domain-name, interface-mtu;
+ require subnet-mask;
+}
diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl
new file mode 100644
index 000000000..83db40c5f
--- /dev/null
+++ b/data/templates/dhcp-client/ipv6.tmpl
@@ -0,0 +1,4 @@
+# generated by ifconfig.py
+interface "{{ ifname }}" {
+ request routers, domain-name-servers, domain-name;
+}
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/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl
index 33f7b2820..f4f2c1848 100644
--- a/data/templates/https/nginx.default.tmpl
+++ b/data/templates/https/nginx.default.tmpl
@@ -43,6 +43,7 @@ server {
location ~ /(retrieve|configure|config-file|image|generate|show) {
{% if server.api %}
proxy_pass http://localhost:{{ server.api.port }};
+ proxy_read_timeout 600;
proxy_buffering off;
{% else %}
return 503;
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 e7715dfb5..a9dacd36e 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -3,18 +3,18 @@
# See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
# for individual keyword definition
-{% if description %}
+{% if description -%}
# {{ description }}
-{% endif %}
+
+{% endif -%}
verb 3
-status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30
-writepid /var/run/openvpn/{{ intf }}.pid
-dev-type {{ type }}
-dev {{ intf }}
user {{ uid }}
group {{ gid }}
+
+dev-type {{ type }}
+dev {{ intf }}
persist-key
iproute /usr/libexec/vyos/system/unpriv-ip
@@ -22,187 +22,197 @@ proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in proto
{%- if local_host %}
local {{ local_host }}
-{% endif %}
+{%- endif %}
{%- if mode == 'server' and protocol == 'udp' and not local_host %}
multihome
-{% endif %}
+{%- endif %}
{%- if local_port %}
lport {{ local_port }}
-{% endif %}
+{%- endif %}
-{%- if remote_port %}
+{% if remote_port -%}
rport {{ remote_port }}
{% endif %}
{%- if remote_host %}
-{% for remote in remote_host -%}
+{%- for remote in remote_host -%}
remote {{ remote }}
{% endfor -%}
-{% endif %}
+{% endif -%}
-{%- if shared_secret_file %}
+{% if shared_secret_file %}
secret {{ shared_secret_file }}
-{% endif %}
+{%- endif %}
{%- if persistent_tunnel %}
persist-tun
-{% endif %}
+{%- endif %}
+
+{%- if redirect_gateway %}
+push "redirect-gateway {{ redirect_gateway }}"
+{%- endif %}
-{%- if mode %}
-{%- if 'client' in mode %}
+{%- if compress_lzo %}
+compress lzo
+{%- endif %}
+
+{% if 'client' in mode -%}
#
# OpenVPN Client mode
#
client
nobind
-{%- elif 'server' in mode %}
+
+{% elif 'server' in mode -%}
#
# OpenVPN Server mode
#
-mode server
-tls-server
-keepalive {{ ping_interval }} {{ ping_restart }}
-management /tmp/openvpn-mgmt-intf unix
{%- if server_topology %}
-topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %}
-{% endif %}
-
-{% for ns in server_dns_nameserver -%}
-push "dhcp-option DNS {{ ns }}"
-{% endfor -%}
+topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %}
+{%- endif %}
-{% for route in server_push_route -%}
-push "route {{ route }}"
-{% endfor -%}
+{%- if bridge_member %}
+mode server
+tls-server
+{%- else %}
+server {{ server_subnet }}{% if server_pool_start %} nopool{% endif %}
+{%- endif %}
-{%- if server_domain %}
-push "dhcp-option DOMAIN {{ server_domain }}"
-{% 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 %}
max-clients {{ server_max_conn }}
-{% endif %}
+{%- endif %}
-{%- if bridge_member %}
-server-bridge nogw
-{%- else %}
-server {{ server_subnet }}
-{% endif %}
+{%- if client %}
+client-config-dir /run/openvpn/ccd/{{ intf }}
+{%- endif %}
{%- if server_reject_unconfigured %}
ccd-exclusive
+{%- endif %}
+
+keepalive {{ ping_interval }} {{ ping_restart }}
+management /tmp/openvpn-mgmt-intf unix
+
+{% for route in server_push_route -%}
+push "route {{ route }}"
+{% endfor -%}
+
+{% for ns in server_dns_nameserver -%}
+push "dhcp-option DNS {{ ns }}"
+{% endfor -%}
+
+{%- if server_domain -%}
+push "dhcp-option DOMAIN {{ server_domain }}"
{% endif %}
-{%- else %}
+{% else -%}
#
# OpenVPN site-2-site mode
#
ping {{ ping_interval }}
ping-restart {{ ping_restart }}
-{%- if local_address_subnet %}
+{% if local_address_subnet -%}
ifconfig {{ local_address }} {{ local_address_subnet }}
-{% elif remote_address %}
+{%- elif remote_address -%}
ifconfig {{ local_address }} {{ remote_address }}
-{% endif %}
+{%- endif %}
-{% endif %}
-{% endif %}
+{% endif -%}
+{% if tls -%}
+# TLS options
{%- if tls_ca_cert %}
ca {{ tls_ca_cert }}
-{% endif %}
+{%- endif %}
{%- if tls_cert %}
cert {{ tls_cert }}
-{% endif %}
+{%- endif %}
{%- if tls_key %}
key {{ tls_key }}
-{% endif %}
+{%- endif %}
{%- if tls_crypt %}
tls-crypt {{ tls_crypt }}
-{% endif %}
+{%- endif %}
{%- if tls_crl %}
crl-verify {{ tls_crl }}
-{% endif %}
+{%- endif %}
{%- if tls_version_min %}
tls-version-min {{tls_version_min}}
-{% endif %}
+{%- endif %}
{%- if tls_dh %}
dh {{ tls_dh }}
-{% endif %}
+{%- endif %}
{%- if tls_auth %}
tls-auth {{tls_auth}}
-{% endif %}
+{%- endif %}
+{%- if tls_role %}
{%- if 'active' in tls_role %}
tls-client
{%- elif 'passive' in tls_role %}
tls-server
-{% endif %}
+{%- endif %}
+{%- endif %}
-{%- if redirect_gateway %}
-push "redirect-gateway {{ redirect_gateway }}"
-{% endif %}
-
-{%- if compress_lzo %}
-compress lzo
-{% endif %}
-
-{%- if hash %}
-auth {{ hash }}
-{% endif %}
+{%- endif %}
+# Encryption options
{%- if encryption %}
-{%- if 'des' in encryption %}
+{% if encryption == 'des' -%}
cipher des-cbc
-{%- elif '3des' in encryption %}
+{%- elif encryption == '3des' -%}
cipher des-ede3-cbc
-{%- elif 'bf128' in encryption %}
+{%- elif encryption == 'bf128' -%}
cipher bf-cbc
keysize 128
-{%- elif 'bf256' in encryption %}
+{%- elif encryption == 'bf256' -%}
cipher bf-cbc
keysize 25
-{%- elif 'aes128gcm' in encryption %}
+{%- elif encryption == 'aes128gcm' -%}
cipher aes-128-gcm
-{%- elif 'aes128' in encryption %}
+{%- elif encryption == 'aes128' -%}
cipher aes-128-cbc
-{%- elif 'aes192gcm' in encryption %}
+{%- elif encryption == 'aes192gcm' -%}
cipher aes-192-gcm
-{%- elif 'aes192' in encryption %}
+{%- elif encryption == 'aes192' -%}
cipher aes-192-cbc
-{%- elif 'aes256gcm' in encryption %}
+{%- elif encryption == 'aes256gcm' -%}
cipher aes-256-gcm
-{%- elif 'aes256' in encryption %}
+{%- elif encryption == 'aes256' -%}
cipher aes-256-cbc
-{% endif %}
-{% endif %}
+{%- endif -%}
+{%- endif %}
{%- if ncp_ciphers %}
ncp-ciphers {{ncp_ciphers}}
-{% endif %}
+{%- endif %}
{%- if disable_ncp %}
ncp-disable
-{% endif %}
+{%- endif %}
+
+{% if hash -%}
+auth {{ hash }}
+{%- endif -%}
{%- if auth %}
auth-user-pass /tmp/openvpn-{{ intf }}-pw
auth-retry nointeract
-{% endif %}
-
-{%- if client %}
-client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
-{% endif %}
+{%- endif %}
# DEPRECATED This option will be removed in OpenVPN 2.5
# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this:
@@ -218,6 +228,12 @@ client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
# See https://phabricator.vyos.net/T1512
compat-names
+{% if options -%}
+#
+# Custom options added by user (not validated)
+#
+
{% for option in options -%}
{{ option }}
{% endfor -%}
+{%- endif %}
diff --git a/data/templates/pppoe/ip-down.script.tmpl b/data/templates/pppoe/ip-down.script.tmpl
index e76875f12..a68fc099c 100644
--- a/data/templates/pppoe/ip-down.script.tmpl
+++ b/data/templates/pppoe/ip-down.script.tmpl
@@ -10,8 +10,9 @@ fi
DIALER_PID=$(cat /var/run/{{ intf }}.pid)
logger -t pppd[$DIALER_PID] "executing $0"
-# Determine if we are enslaved to a VRF, this is needed to properly insert
-# the default route
+{% if not on_demand %}
+# See https://phabricator.vyos.net/T2248. Determine if we are enslaved to a
+# VRF, this is needed to properly insert the default route.
VRF_NAME=""
if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
# Determine upper (VRF) interface
@@ -24,3 +25,4 @@ fi
# Always delete default route when interface goes down
vtysh -c "conf t" ${VRF_NAME} -c "no ip route 0.0.0.0/0 {{ intf }} ${VRF_NAME}"
+{% endif %}
diff --git a/data/templates/pppoe/ip-up.script.tmpl b/data/templates/pppoe/ip-up.script.tmpl
index 4cc779914..697ebcc20 100644
--- a/data/templates/pppoe/ip-up.script.tmpl
+++ b/data/templates/pppoe/ip-up.script.tmpl
@@ -6,13 +6,15 @@ if [ "$6" != "{{ intf }}" ]; then
exit
fi
-set -x
+{% if not on_demand %}
+# See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved
+# to a VRF, this is needed to properly insert the default route.
# add some info to syslog
DIALER_PID=$(cat /var/run/{{ intf }}.pid)
logger -t pppd[$DIALER_PID] "executing $0"
-SED_OPT="ip route"
+SED_OPT="^ip route"
VRF_NAME=""
if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
# Determine upper (VRF) interface
@@ -25,10 +27,8 @@ if [ -d /sys/class/net/{{ intf }}/upper_* ]; then
VRF_NAME="vrf ${VRF}"
fi
-# Debian PPP version has no support for replacing an existing default route
-# thus we emulate this ba an ip-up script https://phabricator.vyos.net/T2220.
{% if 'auto' in default_route -%}
-# only insert a new default route if there is no default route configured
+# Only insert a new default route if there is no default route configured
routes=$(vtysh -c "show running-config" | sed -n "/${SED_OPT}/,/!/p" | grep 0.0.0.0/0 | wc -l)
if [ "$routes" -ne 0 ]; then
exit 1
@@ -44,4 +44,4 @@ done
# Add default route to default or VRF routing table
vtysh -c "conf t" ${VTY_OPT} -c "ip route 0.0.0.0/0 {{ intf }} ${VRF_NAME}"
logger -t pppd[$DIALER_PID] "added default route via {{ intf }} ${VRF_NAME}"
-
+{% endif %}
diff --git a/data/templates/pppoe/peer.tmpl b/data/templates/pppoe/peer.tmpl
index 8651f12a5..36d108cee 100644
--- a/data/templates/pppoe/peer.tmpl
+++ b/data/templates/pppoe/peer.tmpl
@@ -60,4 +60,13 @@ rp_pppoe_service "{{ service_name }}"
{% endif %}
{% if on_demand %}
demand
+# See T2249. PPP default route options should only be set when in on-demand
+# mode. As soon as we are not in on-demand mode the default-route handling is
+# passed to the ip-up.d/ip-down.s scripts which is required for VRF support.
+{% if 'auto' in default_route -%}
+defaultroute
+{% elif 'force' in default_route -%}
+defaultroute
+replacedefaultroute
+{% endif %}
{% endif %}
diff --git a/data/templates/sstp/sstp.config.tmpl b/data/templates/sstp/sstp.config.tmpl
index 19805358e..acdb6c76b 100644
--- a/data/templates/sstp/sstp.config.tmpl
+++ b/data/templates/sstp/sstp.config.tmpl
@@ -30,6 +30,7 @@ disable
[sstp]
verbose=1
+ifname=sstp%d
accept=ssl
ssl-ca-file={{ ssl_ca }}
ssl-pemfile={{ ssl_cert }}
@@ -52,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/control b/debian/control
index bccfc02d4..7b95b2c75 100644
--- a/debian/control
+++ b/debian/control
@@ -32,6 +32,7 @@ Depends: python3,
python3-netaddr,
python3-zmq,
cron,
+ easy-rsa,
ipaddrcheck,
tcpdump,
tshark,
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/include/port-number.xml.i b/interface-definitions/include/port-number.xml.i
new file mode 100644
index 000000000..78eb4b7af
--- /dev/null
+++ b/interface-definitions/include/port-number.xml.i
@@ -0,0 +1,12 @@
+<leafNode name="port">
+ <properties>
+ <help>Port number used to establish connection</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/include/radius-server.xml.i b/interface-definitions/include/radius-server.xml.i
index d1068b0e4..047728233 100644
--- a/interface-definitions/include/radius-server.xml.i
+++ b/interface-definitions/include/radius-server.xml.i
@@ -8,7 +8,7 @@
<help>RADIUS client source address</help>
<valueHelp>
<format>ipv4</format>
- <description>TFTP IPv4 listen address</description>
+ <description>IPv4 source-address of RADIUS queries</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
diff --git a/interface-definitions/include/vif-s.xml.i b/interface-definitions/include/vif-s.xml.i
index 2120aa32d..ab2dcd955 100644
--- a/interface-definitions/include/vif-s.xml.i
+++ b/interface-definitions/include/vif-s.xml.i
@@ -58,6 +58,7 @@
#include <include/interface-disable.xml.i>
#include <include/interface-mac.xml.i>
#include <include/interface-mtu-68-9000.xml.i>
+ #include <include/interface-vrf.xml.i>
</children>
</tagNode>
</children>
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-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 0c56e4e4b..c6e61d19a 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -40,9 +40,9 @@
#include <include/ipv6-dup-addr-detect-transmits.xml.i>
</children>
</node>
- <leafNode name="link">
+ <leafNode name="source-interface">
<properties>
- <help>Lower link device</help>
+ <help>Physical Interface used for this device</help>
<valueHelp>
<format>interface</format>
<description>Interface used for VXLAN underlay</description>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 3ba82067f..e1ac60319 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -107,7 +107,7 @@
</leafNode>
<leafNode name="encapsulation">
<properties>
- <help>Ignore link state changes</help>
+ <help>Encapsulation of this tunnel interface</help>
<completionHelp>
<list>gre gre-bridge ipip sit ipip6 ip6ip6 ip6gre</list>
</completionHelp>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 3108817b3..fdde57525 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -52,9 +52,21 @@
#include <include/ipv6-dup-addr-detect-transmits.xml.i>
</children>
</node>
- <leafNode name="link">
+ <leafNode name="source-address">
<properties>
- <help>Underlay device of VXLAN interface</help>
+ <help>VXLAN source address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 source-address of VXLAN tunnel</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="source-interface">
+ <properties>
+ <help>Physical Interface used for this connection</help>
<valueHelp>
<format>interface</format>
<description>Interface used for VXLAN underlay</description>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index d3f084774..9db608afb 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -19,26 +19,9 @@
#include <include/address-ipv4-ipv6.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
- <leafNode name="port">
- <properties>
- <help>Local port to listen for incoming connections</help>
- <valueHelp>
- <format>1-65535</format>
- <description>Numeric IP port</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="mtu">
- <properties>
- <help>interface mtu size(default: 1420)</help>
- <constraint>
- <validator name="numeric" argument="--range 68-9000"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/interface-vrf.xml.i>
+ #include <include/port-number.xml.i>
+ #include <include/interface-mtu-68-9000.xml.i>
<leafNode name="fwmark">
<properties>
<help>A 32-bit fwmark value set on all outgoing packets</help>
@@ -113,18 +96,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="port">
- <properties>
- <help>Port number on tunnel remote end</help>
- <valueHelp>
- <format>1-65535</format>
- <description>Numeric IP port</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/port-number.xml.i>
<leafNode name="persistent-keepalive">
<properties>
<help>how often send keep alives in seconds</help>
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/l2tp-server.xml.in b/interface-definitions/vpn-l2tp.xml.in
index 7fc844054..d4286a810 100644
--- a/interface-definitions/l2tp-server.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/ipoe-server.xml b/op-mode-definitions/ipoe-server.xml
index 369ceebea..c05e2d2c1 100644
--- a/op-mode-definitions/ipoe-server.xml
+++ b/op-mode-definitions/ipoe-server.xml
@@ -1,5 +1,41 @@
<?xml version="1.0"?>
<interfaceDefinition>
+ <node name="reset">
+ <children>
+ <node name="ipoe-server">
+ <properties>
+ <help>Clear ipoe-server sessions or process</help>
+ </properties>
+ <children>
+ <node name="session">
+ <properties>
+ <help>Clear ipoe-server session</help>
+ </properties>
+ <children>
+ <leafNode name="username">
+ <properties>
+ <help>Clear ipoe-server session by username</help>
+ <completionHelp>
+ <script>/usr/bin/accel-cmd -p 2002 show sessions username | sed -e 's/ \r//g' | tail -n +3</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/accel-cmd -p 2002 terminate username $5</command>
+ </leafNode>
+ <leafNode name="sid">
+ <properties>
+ <help>Clear ipoe-server session by sid</help>
+ <completionHelp>
+ <script>/usr/bin/accel-cmd -p 2002 show sessions sid | sed -e 's/ \r//g' | tail -n +3</script>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/accel-cmd -p 2002 terminate sid $5</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="show">
<children>
<node name="ipoe-server">
@@ -11,7 +47,7 @@
<properties>
<help>Show active IPoE server sessions</help>
</properties>
- <command>/usr/bin/accel-cmd -p 2002 show sessions ifname,called-sid,calling-sid,ip,ip6,ip6-dp,rate-limit,state,uptime,sid</command>
+ <command>/usr/bin/accel-cmd -p 2002 show sessions ifname,username,called-sid,calling-sid,ip,ip6,ip6-dp,rate-limit,state,uptime,sid</command>
</leafNode>
<leafNode name="statistics">
<properties>
diff --git a/op-mode-definitions/reset-vpn.xml b/op-mode-definitions/reset-vpn.xml
index c0b0ddeb1..ae553c272 100644
--- a/op-mode-definitions/reset-vpn.xml
+++ b/op-mode-definitions/reset-vpn.xml
@@ -37,6 +37,12 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="pptp"</command>
</leafNode>
+ <leafNode name="sstp">
+ <properties>
+ <help>Terminate all user's current remote access VPN session(s) with SSTP protocol</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="sstp"</command>
+ </leafNode>
</children>
</node>
</children>
@@ -62,13 +68,19 @@
<properties>
<help>Terminate all user's current remote access VPN session(s) with L2TP protocol</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="l2tp"</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="l2tp"</command>
</leafNode>
<leafNode name="pptp">
<properties>
<help>Terminate all user's current remote access VPN session(s) with PPTP protocol</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="all_users" --protocol="pptp"</command>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="pptp"</command>
+ </leafNode>
+ <leafNode name="sstp">
+ <properties>
+ <help>Terminate all user's current remote access VPN session(s) with SSTP protocol</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/reset_vpn.py --username="$5" --protocol="sstp"</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-raid.xml b/op-mode-definitions/show-raid.xml
index 000fd4610..8bf394552 100644
--- a/op-mode-definitions/show-raid.xml
+++ b/op-mode-definitions/show-raid.xml
@@ -4,7 +4,7 @@
<children>
<tagNode name="raid">
<properties>
- <help>Show statis of RAID set</help>
+ <help>Show status of RAID set</help>
<completionHelp>
<script>${vyos_completion_dir}/list_raidset.sh</script>
</completionHelp>
diff --git a/python/vyos/__init__.py b/python/vyos/__init__.py
index 9b5ed21c9..e3e14fdd8 100644
--- a/python/vyos/__init__.py
+++ b/python/vyos/__init__.py
@@ -1 +1 @@
-from .base import *
+from .base import ConfigError
diff --git a/python/vyos/airbag.py b/python/vyos/airbag.py
new file mode 100644
index 000000000..b0565192d
--- /dev/null
+++ b/python/vyos/airbag.py
@@ -0,0 +1,168 @@
+# Copyright 2019-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/>.
+
+import os
+import sys
+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
+
+
+# we allow to disable the extra logging
+DISABLE = False
+
+
+# emulate a file object
+class _IO(object):
+ def __init__(self, std, log):
+ self.std = std
+ self.log = log
+
+ def write(self, message):
+ self.std.write(message)
+ if DISABLE:
+ return
+ for line in message.split('\n'):
+ s = line.rstrip()
+ if s:
+ self.log(s)
+
+ def flush(self):
+ self.std.flush()
+
+ def close(self):
+ pass
+
+
+# The function which will be used to report information
+# to users when an exception is unhandled
+def bug_report(dtype, value, trace):
+ from traceback import format_exception
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+
+ information = {
+ 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ 'version': get_version(),
+ 'trace': format_exception(dtype, value, trace),
+ 'instructions': COMMUNITY if 'rolling' in get_version() else SUPPORTED,
+ }
+
+ sys.stdout.write(INTRO.format(**information))
+ sys.stdout.flush()
+
+ sys.stderr.write(FAULT.format(**information))
+ sys.stderr.flush()
+
+
+# define an exception handler to be run when an exception
+# reach the end of __main__ and was not intercepted
+def intercepter(dtype, value, trace):
+ bug_report(dtype, value, trace)
+ if debug.enabled('developer'):
+ import pdb
+ pdb.pm()
+
+
+def InterceptingLogger(address, _singleton=[False]):
+ skip = _singleton.pop()
+ _singleton.append(True)
+ if skip:
+ return
+
+ logger = logging.getLogger('VyOS')
+ logger.setLevel(logging.DEBUG)
+ handler = logging.handlers.SysLogHandler(address='/dev/log', facility='syslog')
+ logger.addHandler(handler)
+
+ # log to syslog any message sent to stderr
+ sys.stderr = _IO(sys.stderr, logger.critical)
+
+
+# lists as default arguments in function is normally dangerous
+# as they will keep any modification performed, unless this is
+# what you want to do (in that case to only run the code once)
+def InterceptingException(excepthook,_singleton=[False]):
+ skip = _singleton.pop()
+ _singleton.append(True)
+ if skip:
+ return
+
+ # install the handler to replace the default behaviour
+ # which just prints the exception trace on screen
+ sys.excepthook = excepthook
+
+
+# Do not attempt the extra logging for operational commands
+try:
+ # This fails during boot
+ insession = Config().in_session()
+except:
+ # we save info on boot to help debugging
+ insession = True
+
+
+# Installing the interception, it currently does not work when
+# running testing so we are checking that we are on the router
+# as otherwise it prevents dpkg-buildpackage to work
+if get_version() and insession:
+ InterceptingLogger('/run/systemd/journal/dev-log')
+ InterceptingException(intercepter)
+
+
+# Messages to print
+
+FAULT = """\
+Date: {date}
+VyOS image: {version}
+
+{trace}
+"""
+
+INTRO = """\
+VyOS had an issue completing a command.
+
+We are sorry that you encountered a problem with VyOS.
+There are a few things you can do to help us (and yourself):
+{instructions}
+
+PLEASE, when reporting, do include as much information as you can:
+- do not obfuscate any data (feel free to send us a private communication with
+ the extra information if your business policy is strict on information sharing)
+- and include all the information presented below
+
+"""
+
+COMMUNITY = """\
+- Make sure you are running the latest version of the code available at
+ https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso
+- Consult the forum to see how to handle this issue
+ https://forum.vyos.io
+- Join our community on slack where our users exchange help and advice
+ https://vyos.slack.com
+""".strip()
+
+SUPPORTED = """\
+- Make sure you are running the latest stable version of VyOS
+ the code is available at https://downloads.vyos.io/?dir=release/current
+- Contact us on our online help desk
+ https://support.vyos.io/
+""".strip()
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/defaults.py b/python/vyos/defaults.py
index a2ad142bc..88894674f 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -21,6 +21,7 @@ directories = {
"current": "/opt/vyatta/etc/config-migrate/current",
"migrate": "/opt/vyatta/etc/config-migrate/migrate",
"log": "/var/log/vyatta",
+ "templates": "/usr/share/vyos/templates/"
}
cfg_group = 'vyattacfg'
diff --git a/python/vyos/dicts.py b/python/vyos/dicts.py
new file mode 100644
index 000000000..79cab4a08
--- /dev/null
+++ b/python/vyos/dicts.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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/>.
+
+
+class FixedDict(dict):
+ """
+ FixedDict: A dictionnary not allowing new keys to be created after initialisation.
+
+ >>> f = FixedDict(**{'count':1})
+ >>> f['count'] = 2
+ >>> f['king'] = 3
+ File "...", line ..., in __setitem__
+ raise ConfigError(f'Option "{k}" has no defined default')
+ """
+
+ def __init__(self, **options):
+ self._allowed = options.keys()
+ super().__init__(**options)
+
+ def __setitem__(self, k, v):
+ """
+ __setitem__ is a builtin which is called by python when setting dict values:
+ >>> d = dict()
+ >>> d['key'] = 'value'
+ >>> d
+ {'key': 'value'}
+
+ is syntaxic sugar for
+
+ >>> d = dict()
+ >>> d.__setitem__('key','value')
+ >>> d
+ {'key': 'value'}
+ """
+ if k not in self._allowed:
+ raise ConfigError(f'Option "{k}" has no defined default')
+ super().__setitem__(k, v)
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/bond.py b/python/vyos/ifconfig/bond.py
index e2ff71490..47dd4ff34 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -18,7 +18,8 @@ import os
from vyos.ifconfig.interface import Interface
from vyos.ifconfig.vlan import VLAN
-from vyos.validate import *
+from vyos.validate import assert_list
+from vyos.validate import assert_positive
@Interface.register
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 94b0075d8..44b92c1db 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -16,7 +16,8 @@
from vyos.ifconfig.interface import Interface
-from vyos.validate import *
+from vyos.validate import assert_boolean
+from vyos.validate import assert_positive
@Interface.register
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/dhcp.py b/python/vyos/ifconfig/dhcp.py
index 8ec8263b5..d4ff9c2cd 100644
--- a/python/vyos/ifconfig/dhcp.py
+++ b/python/vyos/ifconfig/dhcp.py
@@ -14,105 +14,37 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import os
-import jinja2
+from vyos.dicts import FixedDict
from vyos.ifconfig.control import Control
+from vyos.template import render
-template_v4 = """
-# generated by ifconfig.py
-option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
-timeout 60;
-retry 300;
-
-interface "{{ intf }}" {
- send host-name "{{ hostname }}";
- {% if client_id -%}
- send dhcp-client-identifier "{{ client_id }}";
- {% endif -%}
- {% if vendor_class_id -%}
- send vendor-class-identifier "{{ vendor_class_id }}";
- {% endif -%}
- request subnet-mask, broadcast-address, routers, domain-name-servers,
- rfc3442-classless-static-routes, domain-name, interface-mtu;
- require subnet-mask;
-}
-
-"""
-
-template_v6 = """
-# generated by ifconfig.py
-interface "{{ intf }}" {
- request routers, domain-name-servers, domain-name;
-}
-
-"""
-
-class DHCP (Control):
+
+class _DHCP (Control):
client_base = r'/var/lib/dhcp/dhclient_'
- def __init__ (self, ifname, **kargs):
+ def __init__(self, ifname, version, **kargs):
super().__init__(**kargs)
-
- # per interface DHCP config files
- self._dhcp = {
- 4: {
- 'ifname': ifname,
- 'conf': self.client_base + ifname + '.conf',
- 'pid': self.client_base + ifname + '.pid',
- 'lease': self.client_base + ifname + '.leases',
- 'options': {
- 'intf': ifname,
- 'hostname': '',
- 'client_id': '',
- 'vendor_class_id': ''
- },
- },
- 6: {
- 'ifname': ifname,
- 'conf': self.client_base + ifname + '.v6conf',
- 'pid': self.client_base + ifname + '.v6pid',
- 'lease': self.client_base + ifname + '.v6leases',
- 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
- 'options': {
- 'intf': ifname,
- 'dhcpv6_prm_only': False,
- 'dhcpv6_temporary': False
- },
- },
+ self.version = version
+ self.file = {
+ 'ifname': ifname,
+ 'conf': self.client_base + ifname + '.' + version + 'conf',
+ 'pid': self.client_base + ifname + '.' + version + 'pid',
+ 'lease': self.client_base + ifname + '.' + version + 'leases',
}
- def get_dhcp_options(self):
- """
- Return dictionary with supported DHCP options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcp[4]['options']
-
- def set_dhcp_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcp[4]['options'] = options
-
- def get_dhcpv6_options(self):
- """
- Return dictionary with supported DHCPv6 options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcp[6]['options']
-
- def set_dhcpv6_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcp[6]['options'] = options
+class _DHCPv4 (_DHCP):
+ def __init__(self, ifname):
+ super().__init__(ifname, '')
+ self.options = FixedDict(**{
+ 'ifname': ifname,
+ 'hostname': '',
+ 'client_id': '',
+ 'vendor_class_id': ''
+ })
# replace dhcpv4/v6 with systemd.networkd?
- def _set_dhcp(self):
+ def set(self):
"""
Configure interface as DHCP client. The dhclient binary is automatically
started in background!
@@ -121,21 +53,16 @@ class DHCP (Control):
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
- >>> j.set_dhcp()
+ >>> j.dhcp.v4.set()
"""
- dhcp = self.get_dhcp_options()
- if not dhcp['hostname']:
+ if not self.options['hostname']:
# read configured system hostname.
# maybe change to vyos hostd client ???
with open('/etc/hostname', 'r') as f:
- dhcp['hostname'] = f.read().rstrip('\n')
+ self.options['hostname'] = f.read().rstrip('\n')
- # render DHCP configuration
- tmpl = jinja2.Template(template_v4)
- dhcp_text = tmpl.render(dhcp)
- with open(self._dhcp[4]['conf'], 'w') as f:
- f.write(dhcp_text)
+ render(self.file['conf'], 'dhcp-client/ipv4.tmpl' ,self.options)
cmd = 'start-stop-daemon'
cmd += ' --start'
@@ -146,9 +73,9 @@ class DHCP (Control):
cmd += ' --'
# now pass arguments to dhclient binary
cmd += ' -4 -nw -cf {conf} -pf {pid} -lf {lease} {ifname}'
- return self._cmd(cmd.format(**self._dhcp[4]))
+ return self._cmd(cmd.format(**self.file))
- def _del_dhcp(self):
+ def delete(self):
"""
De-configure interface as DHCP clinet. All auto generated files like
pid, config and lease will be removed.
@@ -157,14 +84,14 @@ class DHCP (Control):
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
- >>> j.del_dhcp()
+ >>> j.dhcp.v4.delete()
"""
- if not os.path.isfile(self._dhcp[4]['pid']):
+ if not os.path.isfile(self.file['pid']):
self._debug_msg('No DHCP client PID found')
return None
- # with open(self._dhcp[4]['pid'], 'r') as f:
- # pid = int(f.read())
+ # with open(self.file['pid'], 'r') as f:
+ # pid = int(f.read())
# stop dhclient, we need to call dhclient and tell it should release the
# aquired IP address. tcpdump tells me:
@@ -178,14 +105,27 @@ class DHCP (Control):
# Hostname Option 12, length 10: "vyos"
#
cmd = '/sbin/dhclient -cf {conf} -pf {pid} -lf {lease} -r {ifname}'
- self._cmd(cmd.format(**self._dhcp[4]))
+ self._cmd(cmd.format(**self.file))
# cleanup old config files
for name in ('conf', 'pid', 'lease'):
- if os.path.isfile(self._dhcp[4][name]):
- os.remove(self._dhcp[4][name])
+ if os.path.isfile(self.file[name]):
+ os.remove(self.file[name])
+
- def _set_dhcpv6(self):
+class _DHCPv6 (_DHCP):
+ def __init__(self, ifname):
+ super().__init__(ifname, 'v6')
+ self.options = FixedDict(**{
+ 'ifname': ifname,
+ 'dhcpv6_prm_only': False,
+ 'dhcpv6_temporary': False,
+ })
+ self.file.update({
+ 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
+ })
+
+ def set(self):
"""
Configure interface as DHCPv6 client. The dhclient binary is automatically
started in background!
@@ -196,22 +136,17 @@ class DHCP (Control):
>>> j = Interface('eth0')
>>> j.set_dhcpv6()
"""
- dhcpv6 = self.get_dhcpv6_options()
# better save then sorry .. should be checked in interface script
# but if you missed it we are safe!
- if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']:
+ if self.options['dhcpv6_prm_only'] and self.options['dhcpv6_temporary']:
raise Exception(
'DHCPv6 temporary and parameters-only options are mutually exclusive!')
- # render DHCP configuration
- tmpl = jinja2.Template(template_v6)
- dhcpv6_text = tmpl.render(dhcpv6)
- with open(self._dhcp[6]['conf'], 'w') as f:
- f.write(dhcpv6_text)
+ render(self.file['conf'], 'dhcp-client/ipv6.tmpl', self.options)
# no longer accept router announcements on this interface
- self._write_sysfs(self._dhcp[6]['accept_ra'], 0)
+ self._write_sysfs(self.file['accept_ra'], 0)
# assemble command-line to start DHCPv6 client (dhclient)
cmd = 'start-stop-daemon'
@@ -224,15 +159,15 @@ class DHCP (Control):
# now pass arguments to dhclient binary
cmd += ' -6 -nw -cf {conf} -pf {pid} -lf {lease}'
# add optional arguments
- if dhcpv6['dhcpv6_prm_only']:
+ if self.options['dhcpv6_prm_only']:
cmd += ' -S'
- if dhcpv6['dhcpv6_temporary']:
+ if self.options['dhcpv6_temporary']:
cmd += ' -T'
cmd += ' {ifname}'
- return self._cmd(cmd.format(**self._dhcp[6]))
+ return self._cmd(cmd.format(**self.file))
- def _del_dhcpv6(self):
+ def delete(self):
"""
De-configure interface as DHCPv6 clinet. All auto generated files like
pid, config and lease will be removed.
@@ -243,12 +178,12 @@ class DHCP (Control):
>>> j = Interface('eth0')
>>> j.del_dhcpv6()
"""
- if not os.path.isfile(self._dhcp[6]['pid']):
+ if not os.path.isfile(self.file['pid']):
self._debug_msg('No DHCPv6 client PID found')
return None
- # with open(self._dhcp[6]['pid'], 'r') as f:
- # pid = int(f.read())
+ # with open(self.file['pid'], 'r') as f:
+ # pid = int(f.read())
# stop dhclient
cmd = 'start-stop-daemon'
@@ -256,13 +191,18 @@ class DHCP (Control):
cmd += ' --oknodo'
cmd += ' --quiet'
cmd += ' --pidfile {pid}'
- self._cmd(cmd.format(**self._dhcp[6]))
+ self._cmd(cmd.format(**self.file))
# accept router announcements on this interface
- self._write_sysfs(self._dhcp[6]['accept_ra'], 1)
+ self._write_sysfs(self.options['accept_ra'], 1)
# cleanup old config files
for name in ('conf', 'pid', 'lease'):
- if os.path.isfile(self._dhcp[6][name]):
- os.remove(self._dhcp[6][name])
+ if os.path.isfile(self.file[name]):
+ os.remove(self.file[name])
+
+class DHCP (object):
+ def __init__(self, ifname):
+ self.v4 = _DHCPv4(ifname)
+ self.v6 = _DHCPv6(ifname)
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 291b326bf..542de4f59 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -18,8 +18,8 @@ import re
from vyos.ifconfig.interface import Interface
from vyos.ifconfig.vlan import VLAN
+from vyos.validate import assert_list
from vyos.util import run
-from vyos.validate import *
@Interface.register
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/interface.py b/python/vyos/ifconfig/interface.py
index 96057a943..43f823eca 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -18,23 +18,33 @@ import re
import json
import glob
import time
+from time import sleep
+from os.path import isfile
from copy import deepcopy
+from datetime import timedelta
-from vyos.validate import * # should not * include
-from vyos.util import mac2eui64
-from vyos import ConfigError
-
+from hurry.filesize import size, alternative
from ipaddress import IPv4Network, IPv6Address, IPv6Network
from netifaces import ifaddresses, AF_INET, AF_INET6
-from time import sleep
-from os.path import isfile
from tabulate import tabulate
-from hurry.filesize import size,alternative
-from datetime import timedelta
+from vyos.util import mac2eui64
+from vyos import ConfigError
from vyos.ifconfig.dhcp import DHCP
+from vyos.validate import is_ipv4
+from vyos.validate import is_ipv6
+from vyos.validate import is_intf_addr_assigned
+from vyos.validate import assert_boolean
+from vyos.validate import assert_list
+from vyos.validate import assert_mac
+from vyos.validate import assert_mtu
+from vyos.validate import assert_positive
+from vyos.validate import assert_range
+
+from vyos.ifconfig.control import Control
+
-class Interface(DHCP):
+class Interface(Control):
options = []
required = []
default = {
@@ -173,7 +183,8 @@ class Interface(DHCP):
self.config['ifname'] = ifname
# we must have updated config before initialising the Interface
- super().__init__(ifname, **kargs)
+ super().__init__(**kargs)
+ self.dhcp = DHCP(ifname)
if not os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
# Any instance of Interface, such as Interface('eth0')
@@ -216,8 +227,8 @@ class Interface(DHCP):
>>> i.remove()
"""
# stop DHCP(v6) if running
- self._del_dhcp()
- self._del_dhcpv6()
+ self.dhcp.v4.delete()
+ self.dhcp.v6.delete()
# remove all assigned IP addresses from interface - this is a bit redundant
# as the kernel will remove all addresses on interface deletion, but we
@@ -660,12 +671,13 @@ class Interface(DHCP):
# do not change below 'if' ordering esle you will get an exception as:
# ValueError: 'dhcp' does not appear to be an IPv4 or IPv6 address
if addr != 'dhcp' and is_ipv4(addr):
- raise ConfigError("Can't configure both static IPv4 and DHCP address on the same interface")
+ raise ConfigError(
+ "Can't configure both static IPv4 and DHCP address on the same interface")
if addr == 'dhcp':
- self._set_dhcp()
+ self.dhcp.v4.set()
elif addr == 'dhcpv6':
- self._set_dhcpv6()
+ self.dhcp.v6.set()
else:
if not is_intf_addr_assigned(self.config['ifname'], addr):
cmd = 'ip addr add "{}" dev "{}"'.format(addr, self.config['ifname'])
@@ -694,9 +706,9 @@ class Interface(DHCP):
['2001:db8::ffff/64']
"""
if addr == 'dhcp':
- self._del_dhcp()
+ self.dhcp.v4.delete()
elif addr == 'dhcpv6':
- self._del_dhcpv6()
+ self.dhcp.v6.delete()
else:
if is_intf_addr_assigned(self.config['ifname'], addr):
cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname'])
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index 4e4b563a1..55b1a3e91 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -35,10 +35,10 @@ class MACVLANIf(Interface):
'prefixes': ['peth', ],
},
}
- options = Interface.options + ['link', 'mode']
+ options = Interface.options + ['source_interface', 'mode']
def _create(self):
- cmd = 'ip link add {ifname} link {link} type macvlan mode {mode}'.format(
+ cmd = 'ip link add {ifname} link {source_interface} type macvlan mode {mode}'.format(
**self.config)
self._cmd(cmd)
@@ -54,7 +54,7 @@ class MACVLANIf(Interface):
"""
config = {
'address': '',
- 'link': 0,
+ 'source_interface': '',
'mode': ''
}
return config
@@ -62,7 +62,6 @@ class MACVLANIf(Interface):
def set_mode(self, mode):
"""
"""
-
- cmd = 'ip link set dev {} type macvlan mode {}'.format(
- self.config['ifname'], mode)
+ ifname = self.config['ifname']
+ cmd = f'ip link set dev {ifname} type macvlan mode {mode}'
return self._cmd(cmd)
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/stp.py b/python/vyos/ifconfig/stp.py
index 97a3c1ff3..5e83206c2 100644
--- a/python/vyos/ifconfig/stp.py
+++ b/python/vyos/ifconfig/stp.py
@@ -16,7 +16,7 @@
from vyos.ifconfig.interface import Interface
-from vyos.validate import *
+from vyos.validate import assert_positive
class STP:
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 1bbb9eb6a..009a53a82 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -31,7 +31,7 @@ def enable_to_on(value):
raise ValueError(f'expect enable or disable but got "{value}"')
-
+@Interface.register
class _Tunnel(Interface):
"""
_Tunnel: private base class for tunnels
@@ -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/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 5678ad62e..f47ae17cc 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -43,12 +43,13 @@ class VXLANIf(Interface):
default = {
'type': 'vxlan',
- 'vni': 0,
- 'dev': '',
'group': '',
- 'remote': '',
'port': 8472, # The Linux implementation of VXLAN pre-dates
# the IANA's selection of a standard destination port
+ 'remote': '',
+ 'src_address': '',
+ 'src_interface': '',
+ 'vni': 0
}
definition = {
**Interface.definition,
@@ -58,24 +59,30 @@ class VXLANIf(Interface):
'bridgeable': True,
}
}
- options = ['group', 'remote', 'dev', 'port', 'vni']
+ options = ['group', 'remote', 'src_interface', 'port', 'vni', 'src_address']
mapping = {
'ifname': 'add',
'vni': 'id',
'port': 'dstport',
+ 'src_address': 'nolearning local',
}
def _create(self):
cmdline = set()
if self.config['remote']:
- cmdline = ('ifname', 'type', 'remote', 'dev', 'vni', 'port')
- elif self.config['group'] and self.config['dev']:
- cmdline = ('ifname', 'type', 'group', 'dev', 'vni', 'port')
+ cmdline = ('ifname', 'type', 'remote', 'src_interface', 'vni', 'port')
+
+ elif self.config['src_address']:
+ cmdline = ('ifname', 'type', 'src_address', 'vni', 'port')
+
+ elif self.config['group'] and self.config['src_interface']:
+ cmdline = ('ifname', 'type', 'group', 'src_interface', 'vni', 'port')
+
else:
- intf = self.config['intf']
+ ifname = self.config['ifname']
raise ConfigError(
- f'VXLAN "{intf}" is missing mandatory underlay interface for a multicast network.')
+ f'VXLAN "{ifname}" is missing mandatory underlay interface for a multicast network.')
cmd = 'ip link'
for key in cmdline:
diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py
index ed22646c1..899fd17da 100644
--- a/python/vyos/ifconfig_vlan.py
+++ b/python/vyos/ifconfig_vlan.py
@@ -25,32 +25,20 @@ def apply_vlan_config(vlan, config):
if not vlan.definition['vlan']:
raise TypeError()
- # get DHCP config dictionary and update values
- opt = vlan.get_dhcp_options()
-
if config['dhcp_client_id']:
- opt['client_id'] = config['dhcp_client_id']
+ vlan.dhcp.v4.options['client_id'] = config['dhcp_client_id']
if config['dhcp_hostname']:
- opt['hostname'] = config['dhcp_hostname']
+ vlan.dhcp.v4.options['hostname'] = config['dhcp_hostname']
if config['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = config['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- vlan.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = vlan.get_dhcpv6_options()
+ vlan.dhcp.v4.options['vendor_class_id'] = config['dhcp_vendor_class_id']
if config['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ vlan.dhcp.v6.options['dhcpv6_prm_only'] = True
if config['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- vlan.set_dhcpv6_options(opt)
+ vlan.dhcp.v6.options['dhcpv6_temporary'] = True
# update interface description used e.g. within SNMP
vlan.set_alias(config['description'])
diff --git a/python/vyos/ioctl.py b/python/vyos/ioctl.py
index e57d261e4..cfa75aac6 100644
--- a/python/vyos/ioctl.py
+++ b/python/vyos/ioctl.py
@@ -13,9 +13,11 @@
# 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 sys
import os
-import fcntl, struct, sys
-from socket import *
+import socket
+import fcntl
+import struct
SIOCGIFFLAGS = 0x8913
@@ -28,7 +30,7 @@ def get_terminal_size():
def get_interface_flags(intf):
""" Pull the SIOCGIFFLAGS """
nullif = '\0'*256
- sock = socket(AF_INET, SOCK_DGRAM)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
raw = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, intf + nullif)
flags, = struct.unpack('H', raw[16:18])
return flags
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index f0bf41cd4..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, stderr=DEVNULL, universal_newlines=True)
+ 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, stderr=DEVNULL, universal_newlines=True)
+ 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, universal_newlines=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, universal_newlines=True)
+ return cmd(curl_cmd, stderr=None)
except OSError:
return None
diff --git a/python/vyos/template.py b/python/vyos/template.py
new file mode 100644
index 000000000..6c73ce753
--- /dev/null
+++ b/python/vyos/template.py
@@ -0,0 +1,65 @@
+# 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
+
+from jinja2 import Environment
+from jinja2 import FileSystemLoader
+
+from vyos.defaults import directories
+
+
+# reuse the same Environment to improve performance
+_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, 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
+ template: the path to the template relative to the template folder
+ content: the dictionary to use to render the template
+
+ This classes cache the renderer, so rendering the same file multiple time
+ does not cause as too much overhead. If use everywhere, it could be changed
+ and load the template from python environement variables from an import
+ python module generated when the debian package is build
+ (recovering the load time and overhead caused by having the file out of the code)
+ """
+
+ # Setup a renderer for the given template
+ # This is cached and re-used for performance
+ 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 16cfae92d..49c47cd85 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -16,61 +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):
- 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 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:
@@ -86,6 +146,23 @@ def cmd(command, section='', shell=None, input=None, timeout=None, env=None,
return decoded
+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, flag,
+ stdout=stdout, stderr=stderr,
+ input=input, timeout=timeout,
+ env=env, shell=shell,
+ decode=decode,
+ )
+ print(out)
+ return code
+
+
def read_file(path):
""" Read a file to string """
with open(path, 'r') as f:
@@ -93,18 +170,37 @@ def read_file(path):
return data
-def chown_file(path, user, group):
- """ change file owner """
+def chown(path, user, group):
+ """ change file/directory owner """
from pwd import getpwnam
from grp import getgrnam
- if os.path.isfile(path):
+ if os.path.exists(path):
uid = getpwnam(user).pw_uid
gid = getgrnam(group).gr_gid
os.chown(path, uid, gid)
-def chmod_x(path):
- """ make file executable """
+
+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
+
+ if os.path.exists(path):
+ bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP
+ os.chmod(path, bitmask)
+
+
+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/python/vyos/version.py b/python/vyos/version.py
index 383efbc1e..d51a940d6 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -44,7 +44,7 @@ def get_version_data(file=version_file):
file (str): path to the version file
Returns:
- dict: version data
+ dict: version data, if it can not be found and empty dict
The optional ``file`` argument comes in handy in upgrade scripts
that need to retrieve information from images other than the running image.
@@ -52,17 +52,20 @@ def get_version_data(file=version_file):
is an implementation detail and may change in the future, while the interface
of this module will stay the same.
"""
- with open(file, 'r') as f:
- version_data = json.load(f)
- return version_data
+ try:
+ with open(file, 'r') as f:
+ version_data = json.load(f)
+ return version_data
+ except FileNotFoundError:
+ return {}
def get_version(file=None):
"""
- Get the version number
+ Get the version number, or an empty string if it could not be determined
"""
version_data = None
if file:
version_data = get_version_data(file=file)
else:
version_data = get_version_data()
- return version_data["version"]
+ return version_data.get('version','')
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/arp.py b/src/conf_mode/arp.py
index 3daa892d7..fde7dc521 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -22,7 +22,7 @@ import re
import syslog as sl
from vyos.config import Config
-from vyos.util import run
+from vyos.util import call
from vyos import ConfigError
arp_cmd = '/usr/sbin/arp'
@@ -82,12 +82,12 @@ def generate(c):
def apply(c):
for ip_addr in c['remove']:
sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr)
- run(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
+ call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
for ip_addr in c['update']:
sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr])
updated = c['update'][ip_addr]
- run(f'{arp_cmd} -s {ip_addr} {updated}')
+ call(f'{arp_cmd} -s {ip_addr} {updated}')
if __name__ == '__main__':
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py
index f6d90776c..a3bc76ef8 100755
--- a/src/conf_mode/bcast_relay.py
+++ b/src/conf_mode/bcast_relay.py
@@ -19,12 +19,11 @@ import fnmatch
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/udp-broadcast-relay'
@@ -112,11 +111,6 @@ def generate(relay):
if relay is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'bcast-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
config_dir = os.path.dirname(config_file)
config_filename = os.path.basename(config_file)
active_configs = []
@@ -146,16 +140,13 @@ def generate(relay):
# configuration filename contains instance id
file = config_file + str(r['id'])
- tmpl = env.get_template('udp-broadcast-relay.tmpl')
- config_text = tmpl.render(r)
- with open(file, 'w') as f:
- f.write(config_text)
+ render(file, 'bcast-relay/udp-broadcast-relay.tmpl', r)
return None
def apply(relay):
# first stop all running services
- run('sudo systemctl stop udp-broadcast-relay@{1..99}')
+ call('systemctl stop udp-broadcast-relay@{1..99}.service')
if (relay is None) or relay['disabled']:
return None
@@ -165,7 +156,7 @@ def apply(relay):
# Don't start individual instance when it's disabled
if r['disabled']:
continue
- run('sudo systemctl start udp-broadcast-relay@{0}'.format(r['id']))
+ call('systemctl start udp-broadcast-relay@{0}.service'.format(r['id']))
return None
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 1d6d4c6e3..ce0e01308 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -16,15 +16,14 @@
import os
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.template import render
+from vyos.util import call
from vyos import ConfigError
-from vyos.util import run
-config_file = r'/etc/default/isc-dhcp-relay'
+config_file = r'/run/dhcp-relay/dhcp.conf'
default_config_data = {
'interface': [],
@@ -96,28 +95,25 @@ def verify(relay):
def generate(relay):
# bail out early - looks like removal from running config
- if relay is None:
+ if not relay:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('config.tmpl')
- config_text = tmpl.render(relay)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcp-relay/config.tmpl', relay)
return None
def apply(relay):
- if relay is not None:
- run('sudo systemctl restart isc-dhcp-relay.service')
+ if relay:
+ call('systemctl restart isc-dhcp-relay.service')
else:
# DHCP relay support is removed in the commit
- run('sudo systemctl stop isc-dhcp-relay.service')
- os.unlink(config_file)
+ call('systemctl stop isc-dhcp-relay.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
return None
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 69aebe2f4..da01f16eb 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -17,25 +17,19 @@
import os
from ipaddress import ip_address, ip_network
-from jinja2 import FileSystemLoader, Environment
from socket import inet_ntoa
from struct import pack
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_subnet_connected
from vyos import ConfigError
-from vyos.util import run
+from vyos.template import render
+from vyos.util import call, chown
-
-config_file = r'/etc/dhcp/dhcpd.conf'
-lease_file = r'/config/dhcpd.leases'
-pid_file = r'/var/run/dhcpd.pid'
-daemon_config_file = r'/etc/default/isc-dhcpv4-server'
+config_file = r'/run/dhcp-server/dhcpd.conf'
default_config_data = {
- 'lease_file': lease_file,
'disabled': False,
'ddns_enable': False,
'global_parameters': [],
@@ -451,7 +445,7 @@ def get_config():
return dhcp
def verify(dhcp):
- if (dhcp is None) or (dhcp['disabled'] is True):
+ if not dhcp or dhcp['disabled']:
return None
# If DHCP is enabled we need one share-network
@@ -597,49 +591,29 @@ def verify(dhcp):
return None
def generate(dhcp):
- if dhcp is None:
- return None
-
- if dhcp['disabled'] is True:
- print('Warning: DHCP server will be deactivated because it is disabled')
+ if not dhcp or dhcp['disabled']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcp-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
- tmpl = env.get_template('dhcpd.conf.tmpl')
- config_text = tmpl.render(dhcp)
# Please see: https://phabricator.vyos.net/T1129 for quoting of the raw parameters
# we can pass to ISC DHCPd
- config_text = config_text.replace("&quot;",'"')
-
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render(dhcp)
- with open(daemon_config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ formater=lambda _: _.replace("&quot;", '"'))
return None
def apply(dhcp):
- if (dhcp is None) or dhcp['disabled']:
+ if not dhcp or dhcp['disabled']:
# DHCP server is removed in the commit
- run('sudo systemctl stop isc-dhcpv4-server.service')
+ call('systemctl stop isc-dhcp-server.service')
if os.path.exists(config_file):
os.unlink(config_file)
- if os.path.exists(daemon_config_file):
- os.unlink(daemon_config_file)
- else:
- # If our file holding DHCP leases does yet not exist - create it
- if not os.path.exists(lease_file):
- os.mknod(lease_file)
-
- run('sudo systemctl restart isc-dhcpv4-server.service')
+ return None
+ call('systemctl restart isc-dhcp-server.service')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index a67deb6c7..cb5a4bbfb 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -18,15 +18,13 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
-
-config_file = r'/etc/default/isc-dhcpv6-relay'
+config_file = r'/run/dhcp-relay/dhcpv6.conf'
default_config_data = {
'listen_addr': [],
@@ -86,25 +84,22 @@ def generate(relay):
if relay is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-relay')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('config.tmpl')
- config_text = tmpl.render(relay)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcpv6-relay/config.tmpl', relay)
return None
def apply(relay):
if relay is not None:
- run('sudo systemctl restart isc-dhcpv6-relay.service')
+ call('systemctl restart isc-dhcp-relay6.service')
else:
# DHCPv6 relay support is removed in the commit
- run('sudo systemctl stop isc-dhcpv6-relay.service')
- os.unlink(config_file)
+ call('systemctl stop isc-dhcp-relay6.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
return None
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 003e80915..94a307826 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -19,22 +19,16 @@ import ipaddress
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.template import render
+from vyos.util import call
from vyos.validate import is_subnet_connected
from vyos import ConfigError
-from vyos.util import run
-
-config_file = r'/etc/dhcp/dhcpdv6.conf'
-lease_file = r'/config/dhcpdv6.leases'
-pid_file = r'/var/run/dhcpdv6.pid'
-daemon_config_file = r'/etc/default/isc-dhcpv6-server'
+config_file = r'/run/dhcp-server/dhcpdv6.conf'
default_config_data = {
- 'lease_file': lease_file,
'preference': '',
'disabled': False,
'shared_network': []
@@ -222,10 +216,7 @@ def get_config():
return dhcpv6
def verify(dhcpv6):
- if dhcpv6 is None:
- return None
-
- if dhcpv6['disabled']:
+ if not dhcpv6 or dhcpv6['disabled']:
return None
# If DHCP is enabled we need one share-network
@@ -337,44 +328,25 @@ def verify(dhcpv6):
return None
def generate(dhcpv6):
- if dhcpv6 is None:
- return None
-
- if dhcpv6['disabled']:
- print('Warning: DHCPv6 server will be deactivated because it is disabled')
+ if not dhcpv6 or dhcpv6['disabled']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dhcpv6-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('dhcpdv6.conf.tmpl')
- config_text = tmpl.render(dhcpv6)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render(dhcpv6)
- with open(daemon_config_file, 'w') as f:
- f.write(config_text)
+ # Create configuration directory on demand
+ dirname = os.path.dirname(config_file)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ render(config_file, 'dhcpv6-server/dhcpdv6.conf.tmpl', dhcpv6)
return None
def apply(dhcpv6):
- if (dhcpv6 is None) or dhcpv6['disabled']:
+ if not dhcpv6 or dhcpv6['disabled']:
# DHCP server is removed in the commit
- run('sudo systemctl stop isc-dhcpv6-server.service')
+ call('systemctl stop isc-dhcp-server6.service')
if os.path.exists(config_file):
os.unlink(config_file)
- if os.path.exists(daemon_config_file):
- os.unlink(daemon_config_file)
- else:
- # If our file holding DHCPv6 leases does yet not exist - create it
- if not os.path.exists(lease_file):
- os.mknod(lease_file)
- run('sudo systemctl restart isc-dhcpv6-server.service')
+ call('systemctl restart isc-dhcp-server6.service')
return None
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 5dc599425..567dfa4b3 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -19,20 +19,19 @@ import argparse
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.hostsd_client import Client as hostsd_client
from vyos.util import wait_for_commit_lock
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
parser = argparse.ArgumentParser()
parser.add_argument("--dhclient", action="store_true",
help="Started from dhclient-script")
-config_file = r'/etc/powerdns/recursor.conf'
+config_file = r'/run/powerdns/recursor.conf'
default_config_data = {
'allow_from': [],
@@ -153,25 +152,21 @@ def generate(dns):
if dns is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dns-forwarding')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
+ dirname = os.path.dirname(config_file)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
- tmpl = env.get_template('recursor.conf.tmpl')
- config_text = tmpl.render(dns)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'dns-forwarding/recursor.conf.tmpl', dns, trim_blocks=True)
return None
def apply(dns):
if dns is None:
# DNS forwarding is removed in the commit
- run("systemctl stop pdns-recursor")
+ call("systemctl stop pdns-recursor.service")
if os.path.isfile(config_file):
os.unlink(config_file)
else:
- run("systemctl restart pdns-recursor")
+ call("systemctl restart pdns-recursor.service")
if __name__ == '__main__':
args = parser.parse_args()
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index b9163f7b3..038f77cf9 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -18,18 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from stat import S_IRUSR, S_IWUSR
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
-
-config_file = r'/etc/ddclient/ddclient.conf'
-cache_file = r'/var/cache/ddclient/ddclient.cache'
-pid_file = r'/var/run/ddclient/ddclient.pid'
+config_file = r'/run/ddclient/ddclient.conf'
# Mapping of service name to service protocol
default_service_protocol = {
@@ -48,9 +44,7 @@ default_service_protocol = {
default_config_data = {
'interfaces': [],
- 'cache_file': cache_file,
- 'deleted': False,
- 'pid_file': pid_file
+ 'deleted': False
}
def get_config():
@@ -221,28 +215,13 @@ def verify(dyndns):
def generate(dyndns):
# bail out early - looks like removal from running config
if dyndns['deleted']:
- if os.path.exists(config_file):
- os.unlink(config_file)
-
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'dynamic-dns')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- dirname = os.path.dirname(dyndns['pid_file'])
- if not os.path.exists(dirname):
- os.mkdir(dirname)
-
dirname = os.path.dirname(config_file)
if not os.path.exists(dirname):
os.mkdir(dirname)
- tmpl = env.get_template('ddclient.conf.tmpl')
- config_text = tmpl.render(dyndns)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
# Config file must be accessible only by its owner
os.chmod(config_file, S_IRUSR | S_IWUSR)
@@ -250,18 +229,13 @@ def generate(dyndns):
return None
def apply(dyndns):
- if os.path.exists(dyndns['cache_file']):
- os.unlink(dyndns['cache_file'])
-
- if os.path.exists('/etc/ddclient.conf'):
- os.unlink('/etc/ddclient.conf')
-
if dyndns['deleted']:
- run('/etc/init.d/ddclient stop')
- if os.path.exists(dyndns['pid_file']):
- os.unlink(dyndns['pid_file'])
+ call('systemctl stop ddclient.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
+
else:
- run('/etc/init.d/ddclient restart')
+ call('systemctl restart ddclient.service')
return None
diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py
index 90f004bc4..0b800f48f 100755
--- a/src/conf_mode/firewall_options.py
+++ b/src/conf_mode/firewall_options.py
@@ -21,7 +21,7 @@ import copy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -87,19 +87,19 @@ def apply(tcp):
target = 'VYOS_FW_OPTIONS'
# always cleanup iptables
- run('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- run('iptables --table mangle --flush {} >&/dev/null'.format(target))
- run('iptables --table mangle --delete-chain {} >&/dev/null'.format(target))
+ call('iptables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
+ call('iptables --table mangle --flush {} >&/dev/null'.format(target))
+ call('iptables --table mangle --delete-chain {} >&/dev/null'.format(target))
# always cleanup ip6tables
- run('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --flush {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --delete FORWARD --jump {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --flush {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --delete-chain {} >&/dev/null'.format(target))
# Setup new iptables rules
if tcp['new_chain4']:
- run('iptables --table mangle --new-chain {} >&/dev/null'.format(target))
- run('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
+ call('iptables --table mangle --new-chain {} >&/dev/null'.format(target))
+ call('iptables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
for opts in tcp['intf_opts']:
intf = opts['intf']
@@ -111,13 +111,13 @@ def apply(tcp):
# adjust TCP MSS per interface
if mss:
- run('iptables --table mangle --append {} --out-interface {} --protocol tcp ' \
+ call('iptables --table mangle --append {} --out-interface {} --protocol tcp '
'--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
# Setup new ip6tables rules
if tcp['new_chain6']:
- run('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target))
- run('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --new-chain {} >&/dev/null'.format(target))
+ call('ip6tables --table mangle --append FORWARD --jump {} >&/dev/null'.format(target))
for opts in tcp['intf_opts']:
intf = opts['intf']
@@ -129,7 +129,7 @@ def apply(tcp):
# adjust TCP MSS per interface
if mss:
- run('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
+ call('ip6tables --table mangle --append {} --out-interface {} --protocol tcp '
'--tcp-flags SYN,RST SYN --jump TCPMSS --set-mss {} >&/dev/null'.format(target, intf, mss))
return None
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 1008f3fae..1354488ac 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -16,17 +16,18 @@
import os
import re
+from sys import exit
import ipaddress
from ipaddress import ip_address
from jinja2 import FileSystemLoader, Environment
-from sys import exit
+from vyos.ifconfig import Section
from vyos.ifconfig import Interface
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import cmd
+from vyos.render import render
# default values
@@ -60,7 +61,7 @@ def _sflow_default_agentip(config):
return config.return_value('protocols ospfv3 parameters router-id')
# if router-id was not found, use first available ip of any interface
- for iface in Interface.listing():
+ for iface in Section.interfaces():
for address in Interface(iface).get_addr():
# return an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -82,7 +83,7 @@ def _iptables_get_nflog():
for iptables_variant in ['iptables', 'ip6tables']:
# run iptables, save output and split it by lines
iptables_command = "sudo {0} -t {1} -S {2}".format(iptables_variant, iptables_nflog_table, iptables_nflog_chain)
- cmd(iptables_command, universal_newlines=True, message='Failed to get flows list')
+ cmd(iptables_command, message='Failed to get flows list')
iptables_out = stdout.splitlines()
# parse each line and add information to list
@@ -234,7 +235,7 @@ def verify(config):
# check that all configured interfaces exists in the system
for iface in config['interfaces']:
- if not iface in Interface.listing():
+ if not iface in Section.interfaces():
# chnged from error to warning to allow adding dynamic interfaces and interface templates
# raise ConfigError("The {} interface is not presented in the system".format(iface))
print("Warning: the {} interface is not presented in the system".format(iface))
@@ -262,7 +263,7 @@ def verify(config):
# check if configured sFlow agent-id exist in the system
agent_id_presented = None
- for iface in Interface.listing():
+ for iface in Section.interfaces():
for address in Interface(iface).get_addr():
# check an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -334,16 +335,10 @@ def generate(config):
timeout_string = "{}:{}={}".format(timeout_string, timeout_type, timeout_value)
config['netflow']['timeout_string'] = timeout_string
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'netflow')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- # Generate daemon configs
- tmpl = env.get_template('uacctd.conf.tmpl')
- config_text = tmpl.render(templatecfg = config, snaplen = default_captured_packet_size)
- with open(uacctd_conf_path, 'w') as file:
- file.write(config_text)
+ render(uacctd_conf_path, 'netflow/uacctd.conf.tmpl', {
+ 'templatecfg': config,
+ 'snaplen': default_captured_packet_size,
+ })
def apply(config):
@@ -351,9 +346,9 @@ def apply(config):
command = None
# Check if flow-accounting was removed and define command
if not config['flow-accounting-configured']:
- command = '/usr/bin/sudo /bin/systemctl stop uacctd'
+ command = 'systemctl stop uacctd.service'
else:
- command = '/usr/bin/sudo /bin/systemctl restart uacctd'
+ command = 'systemctl restart uacctd.service'
# run command to start or stop flow-accounting
cmd(command, raising=ConfigError, message='Failed to start/stop flow-accounting')
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 690d1e030..dd5819f9f 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -33,7 +33,9 @@ import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import run
default_config_data = {
@@ -157,21 +159,21 @@ def apply(config):
# rsyslog runs into a race condition at boot time with systemd
# restart rsyslog only if the hostname changed.
hostname_old = cmd('hostnamectl --static')
- cmd(f'hostnamectl set-hostname --static {hostname_new}')
+ call(f'hostnamectl set-hostname --static {hostname_new}')
# Restart services that use the hostname
if hostname_new != hostname_old:
- run("systemctl restart rsyslog.service")
+ call("systemctl restart rsyslog.service")
# If SNMP is running, restart it too
- ret = run("pgrep snmpd > /dev/null")
+ ret = run("pgrep snmpd")
if ret == 0:
- run("systemctl restart snmpd.service")
+ call("systemctl restart snmpd.service")
# restart pdns if it is used
- ret = run('/usr/bin/rec_control ping >/dev/null 2>&1')
+ ret = run('/usr/bin/rec_control ping')
if ret == 0:
- run('/etc/init.d/pdns-recursor restart >/dev/null')
+ call('systemctl restart pdns-recursor.service')
return None
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 91b8aa34b..26f4aea7f 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -24,7 +24,8 @@ from copy import deepcopy
import vyos.defaults
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
config_file = '/etc/vyos/http-api.conf'
@@ -91,9 +92,9 @@ def generate(http_api):
def apply(http_api):
if http_api is not None:
- run('sudo systemctl restart vyos-http-api.service')
+ call('sudo systemctl restart vyos-http-api.service')
else:
- run('sudo systemctl stop vyos-http-api.service')
+ call('sudo systemctl stop vyos-http-api.service')
for dep in dependencies:
cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError)
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index 777792229..7d3a1b9cb 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -18,15 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
import vyos.defaults
import vyos.certbot_util
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = '/etc/nginx/sites-available/default'
@@ -133,26 +132,18 @@ def generate(https):
if https is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'https')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
if 'server_block_list' not in https or not https['server_block_list']:
https['server_block_list'] = [default_server_block]
- tmpl = env.get_template('nginx.default.tmpl')
- config_text = tmpl.render(https)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True)
return None
def apply(https):
if https is not None:
- run('sudo systemctl restart nginx.service')
+ call('sudo systemctl restart nginx.service')
else:
- run('sudo systemctl stop nginx.service')
+ call('sudo systemctl stop nginx.service')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index abe473530..9fa591a2c 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -18,13 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/igmpproxy.conf'
@@ -116,26 +115,17 @@ def generate(igmp_proxy):
print('Warning: IGMP Proxy will be deactivated because it is disabled')
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp-proxy')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('igmpproxy.conf.tmpl')
- config_text = tmpl.render(igmp_proxy)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy)
return None
def apply(igmp_proxy):
if igmp_proxy is None or igmp_proxy['disable']:
# IGMP Proxy support is removed in the commit
- run('sudo systemctl stop igmpproxy.service')
+ call('sudo systemctl stop igmpproxy.service')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- run('systemctl restart igmpproxy.service')
+ call('systemctl restart igmpproxy.service')
return None
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index 6a002bc06..fd1f218d1 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -24,7 +24,8 @@ from vyos.ifconfig import BondIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
-from vyos.util import run, is_bridge_member
+from vyos.util import is_bridge_member
+from vyos.util import call
from vyos import ConfigError
default_config_data = {
@@ -91,7 +92,7 @@ def get_config():
if not os.path.isfile('/sys/class/net/bonding_masters'):
import syslog
syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module")
- if run('modprobe bonding max_bonds=0 miimon=250') != 0:
+ if call('modprobe bonding max_bonds=0 miimon=250') != 0:
syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module")
raise ConfigError("failed loading bonding kernel module")
@@ -398,32 +399,20 @@ def apply(bond):
# update interface description used e.g. within SNMP
b.set_alias(bond['description'])
- # get DHCP config dictionary and update values
- opt = b.get_dhcp_options()
-
if bond['dhcp_client_id']:
- opt['client_id'] = bond['dhcp_client_id']
+ b.dhcp.v4.options['client_id'] = bond['dhcp_client_id']
if bond['dhcp_hostname']:
- opt['hostname'] = bond['dhcp_hostname']
+ b.dhcp.v4.options['hostname'] = bond['dhcp_hostname']
if bond['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = bond['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- b.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = b.get_dhcpv6_options()
+ b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id']
if bond['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ b.dhcp.v6.options['dhcpv6_prm_only'] = True
if bond['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are required
- b.set_dhcpv6_options(opt)
+ b.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
b.set_link_detect(bond['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 79247ee51..93c6db97e 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -300,32 +300,20 @@ def apply(bridge):
# update interface description used e.g. within SNMP
br.set_alias(bridge['description'])
- # get DHCP config dictionary and update values
- opt = br.get_dhcp_options()
-
if bridge['dhcp_client_id']:
- opt['client_id'] = bridge['dhcp_client_id']
+ br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id']
if bridge['dhcp_hostname']:
- opt['hostname'] = bridge['dhcp_hostname']
+ br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname']
if bridge['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = bridge['dhcp_vendor_class_id']
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- br.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = br.get_dhcpv6_options()
+ br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id']
if bridge['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ br.dhcp.v6.options['dhcpv6_prm_only'] = True
if bridge['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- br.set_dhcpv6_options(opt)
+ br.dhcp.v6.options['dhcpv6_temporary'] = True
# assign/remove VRF
br.set_vrf(bridge['vrf'])
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 15e9b4185..5a977d797 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -301,32 +301,20 @@ def apply(eth):
# update interface description used e.g. within SNMP
e.set_alias(eth['description'])
- # get DHCP config dictionary and update values
- opt = e.get_dhcp_options()
-
if eth['dhcp_client_id']:
- opt['client_id'] = eth['dhcp_client_id']
+ e.dhcp.v4.options['client_id'] = eth['dhcp_client_id']
if eth['dhcp_hostname']:
- opt['hostname'] = eth['dhcp_hostname']
+ e.dhcp.v4.options['hostname'] = eth['dhcp_hostname']
if eth['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = eth['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- e.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = e.get_dhcpv6_options()
+ e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id']
if eth['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ e.dhcp.v6.options['dhcpv6_prm_only'] = True
if eth['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- e.set_dhcpv6_options(opt)
+ e.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
e.set_link_detect(eth['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 0400cb849..11ba9acdd 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -22,7 +22,8 @@ from copy import deepcopy
from vyos.config import Config
from vyos.ifconfig import L2TPv3If, Interface
from vyos import ConfigError
-from vyos.util import run, is_bridge_member
+from vyos.util import call
+from vyos.util import is_bridge_member
from netifaces import interfaces
default_config_data = {
@@ -51,7 +52,7 @@ def check_kmod():
modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6']
for module in modules:
if not os.path.exists(f'/sys/module/{module}'):
- if run(f'modprobe {module}') != 0:
+ if call(f'modprobe {module}') != 0:
raise ConfigError(f'Loading Kernel module {module} failed')
def get_config():
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index e9b40bb38..b42765586 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -17,23 +17,20 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from copy import deepcopy
-from sys import exit
-from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH
-from grp import getgrnam
-from ipaddress import ip_address,ip_network,IPv4Interface
+from sys import exit,stderr
+from ipaddress import IPv4Address,IPv4Network,summarize_address_range
from netifaces import interfaces
-from pwd import getpwnam
from time import sleep
from shutil import rmtree
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import VTunIf
-from vyos.util import process_running, cmd, is_bridge_member
+from vyos.util import call, is_bridge_member, chown, chmod_600, chmod_755
from vyos.validate import is_addr_assigned
from vyos import ConfigError
+from vyos.template import render
+
user = 'openvpn'
group = 'openvpn'
@@ -75,10 +72,14 @@ default_config_data = {
'server_domain': '',
'server_max_conn': '',
'server_dns_nameserver': [],
+ 'server_pool': False,
+ 'server_pool_start': '',
+ 'server_pool_stop': '',
+ 'server_pool_netmask': '',
'server_push_route': [],
'server_reject_unconfigured': False,
'server_subnet': '',
- 'server_topology': '',
+ 'server_topology': 'net30',
'shared_secret_file': '',
'tls': False,
'tls_auth': '',
@@ -97,32 +98,9 @@ default_config_data = {
def get_config_name(intf):
- cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
+ cfg_file = f'/run/openvpn/{intf}.conf'
return cfg_file
-def openvpn_mkdir(directory):
- # create directory on demand
- if not os.path.exists(directory):
- os.mkdir(directory)
-
- # fix permissions - corresponds to mode 755
- os.chmod(directory, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
- os.chown(directory, uid, gid)
-
-def fixup_permission(filename, permission=S_IRUSR):
- """
- Check if the given file exists and change ownershit to root/vyattacfg
- and appripriate file access permissions - default is user and group readable
- """
- if os.path.isfile(filename):
- os.chmod(filename, permission)
-
- # make file owned by root / vyattacfg
- uid = getpwnam('root').pw_uid
- gid = getgrnam('vyattacfg').gr_gid
- os.chown(filename, uid, gid)
def checkCertHeader(header, filename):
"""
@@ -139,6 +117,66 @@ def checkCertHeader(header, filename):
return False
+def getDefaultServer(network, topology, devtype):
+ """
+ Gets the default server parameters for a "server" directive.
+ Currently only IPv4 routed but may be extended to support bridged and/or IPv6 in the future.
+ Logic from openvpn's src/openvpn/helper.c.
+ Returns a dict with addresses or False if the input parameters were incorrect.
+ """
+ if not (topology and devtype):
+ return False
+
+ if not (devtype == 'tun' or devtype == 'tap'):
+ return False
+
+ if not network.prefixlen:
+ return False
+ elif (devtype == 'tun' and network.prefixlen > 29) or (devtype == 'tap' and network.prefixlen > 30):
+ return False
+
+ server = {
+ 'local': '',
+ 'remote_netmask': '',
+ 'client_remote_netmask': '',
+ 'pool_start': '',
+ 'pool_stop': '',
+ 'pool_netmask': ''
+ }
+
+ if devtype == 'tun':
+ if topology == 'net30' or topology == 'point-to-point':
+ server['local'] = network[1]
+ server['remote_netmask'] = network[2]
+ server['client_remote_netmask'] = server['local']
+
+ # pool start is 4th host IP in subnet (.4 in a /24)
+ server['pool_start'] = network[4]
+
+ if network.prefixlen == 29:
+ server['pool_stop'] = network.broadcast_address
+ else:
+ # pool end is -4 from the broadcast address (.251 in a /24)
+ server['pool_stop'] = network[-5]
+
+ elif topology == 'subnet':
+ server['local'] = network[1]
+ server['remote_netmask'] = str(network.netmask)
+ server['client_remote_netmask'] = server['remote_netmask']
+ server['pool_start'] = network[2]
+ server['pool_stop'] = network[-3]
+ server['pool_netmask'] = server['remote_netmask']
+
+ elif devtype == 'tap':
+ server['local'] = network[1]
+ server['remote_netmask'] = str(network.netmask)
+ server['client_remote_netmask'] = server['remote_netmask']
+ server['pool_start'] = network[2]
+ server['pool_stop'] = network[-2]
+ server['pool_netmask'] = server['remote_netmask']
+
+ return server
+
def get_config():
openvpn = deepcopy(default_config_data)
conf = Config()
@@ -308,10 +346,10 @@ def get_config():
# Server-mode subnet (from which client IPs are allocated)
if conf.exists('server subnet'):
- network = conf.return_value('server subnet')
- tmp = IPv4Interface(network).with_netmask
+ # server_network is used later in this function
+ server_network = IPv4Network(conf.return_value('server subnet'))
# convert the network in format: "192.0.2.0 255.255.255.0" for later use in template
- openvpn['server_subnet'] = tmp.replace(r'/', ' ')
+ openvpn['server_subnet'] = server_network.with_netmask.replace(r'/', ' ')
# Client-specific settings
for client in conf.list_nodes('server client'):
@@ -326,19 +364,6 @@ def get_config():
'remote_netmask': ''
}
- # note: with "topology subnet", this is "<ip> <netmask>".
- # with "topology p2p", this is "<ip> <our_ip>".
- if openvpn['server_topology'] == 'subnet':
- # we are only interested in the netmask portion of server_subnet
- data['remote_netmask'] = openvpn['server_subnet'].split(' ')[1]
- else:
- # we need the server subnet in format 192.0.2.0/255.255.255.0
- subnet = openvpn['server_subnet'].replace(' ', r'/')
- # get iterator over the usable hosts in the network
- tmp = ip_network(subnet).hosts()
- # OpenVPN always uses the subnets first available IP address
- data['remote_netmask'] = list(tmp)[0]
-
# Option to disable client connection
if conf.exists('disable'):
data['disable'] = True
@@ -349,13 +374,11 @@ def get_config():
# Route to be pushed to the client
for network in conf.return_values('push-route'):
- tmp = IPv4Interface(network).with_netmask
- data['push_route'].append(tmp.replace(r'/', ' '))
+ data['push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Subnet belonging to the client
for network in conf.return_values('subnet'):
- tmp = IPv4Interface(network).with_netmask
- data['subnet'].append(tmp.replace(r'/', ' '))
+ data['subnet'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Append to global client list
openvpn['client'].append(data)
@@ -363,6 +386,19 @@ def get_config():
# re-set configuration level
conf.set_level('interfaces openvpn ' + openvpn['intf'])
+ # Server client IP pool
+ if conf.exists('server client-ip-pool'):
+ openvpn['server_pool'] = True
+
+ if conf.exists('server client-ip-pool start'):
+ openvpn['server_pool_start'] = conf.return_value('server client-ip-pool start')
+
+ if conf.exists('server client-ip-pool stop'):
+ openvpn['server_pool_stop'] = conf.return_value('server client-ip-pool stop')
+
+ if conf.exists('server client-ip-pool netmask'):
+ openvpn['server_pool_netmask'] = conf.return_value('server client-ip-pool netmask')
+
# DNS suffix to be pushed to all clients
if conf.exists('server domain-name'):
openvpn['server_domain'] = conf.return_value('server domain-name')
@@ -378,8 +414,7 @@ def get_config():
# Route to be pushed to all clients
if conf.exists('server push-route'):
for network in conf.return_values('server push-route'):
- tmp = IPv4Interface(network).with_netmask
- openvpn['server_push_route'].append(tmp.replace(r'/', ' '))
+ openvpn['server_push_route'].append(IPv4Network(network).with_netmask.replace(r'/', ' '))
# Reject connections from clients that are not explicitly configured
if conf.exists('server reject-unconfigured-clients'):
@@ -428,6 +463,7 @@ def get_config():
# Minimum required TLS version
if conf.exists('tls tls-version-min'):
openvpn['tls_version_min'] = conf.return_value('tls tls-version-min')
+ openvpn['tls'] = True
if conf.exists('shared-secret-key-file'):
openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file')
@@ -440,6 +476,26 @@ def get_config():
if not openvpn['tls_dh'] and openvpn['tls_key'] and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
openvpn['tls_dh'] = 'none'
+ # Set defaults where necessary.
+ # If any of the input parameters are missing or wrong,
+ # this will return False and no defaults will be set.
+ default_server = getDefaultServer(server_network, openvpn['server_topology'], openvpn['type'])
+ if default_server:
+ # server-bridge doesn't require a pool so don't set defaults for it
+ if not openvpn['bridge_member']:
+ openvpn['server_pool'] = True
+ if not openvpn['server_pool_start']:
+ openvpn['server_pool_start'] = default_server['pool_start']
+
+ if not openvpn['server_pool_stop']:
+ openvpn['server_pool_stop'] = default_server['pool_stop']
+
+ if not openvpn['server_pool_netmask']:
+ openvpn['server_pool_netmask'] = default_server['pool_netmask']
+
+ for client in openvpn['client']:
+ client['remote_netmask'] = default_server['client_remote_netmask']
+
return openvpn
def verify(openvpn):
@@ -489,7 +545,11 @@ def verify(openvpn):
# OpenVPN site-to-site - VERIFY
#
if openvpn['mode'] == 'site-to-site':
- if not (openvpn['local_address'] or openvpn['bridge_member']):
+ if openvpn['ncp_ciphers']:
+ raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
+
+ if openvpn['mode'] == 'site-to-site' and not openvpn['bridge_member']:
+ if not openvpn['local_address']:
raise ConfigError('Must specify "local-address" or "bridge member interface"')
for host in openvpn['remote_host']:
@@ -506,15 +566,10 @@ def verify(openvpn):
if openvpn['local_address'] == openvpn['local_host']:
raise ConfigError('"local-address" cannot be the same as "local-host"')
- if openvpn['ncp_ciphers']:
- raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client')
-
else:
+ # checks for client-server or site-to-site bridged
if openvpn['local_address'] or openvpn['remote_address']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server mode')
-
- elif openvpn['bridge_member']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in bridge mode')
+ raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server or bridge mode')
#
# OpenVPN server mode - VERIFY
@@ -535,9 +590,41 @@ def verify(openvpn):
if not openvpn['tls_dh'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls_key']):
raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode')
- if not openvpn['server_subnet']:
+ if openvpn['server_subnet']:
+ subnet = IPv4Network(openvpn['server_subnet'].replace(' ', '/'))
+
+ if openvpn['type'] == 'tun' and subnet.prefixlen > 29:
+ raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported')
+ elif openvpn['type'] == 'tap' and subnet.prefixlen > 30:
+ raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported')
+
+ for client in openvpn['client']:
+ if client['ip'] and not IPv4Address(client['ip']) in subnet:
+ raise ConfigError(f'Client IP "{client["ip"]}" not in server subnet "{subnet}"')
+
+ else:
if not openvpn['bridge_member']:
- raise ConfigError('Must specify "server subnet" option in server mode')
+ raise ConfigError('Must specify "server subnet" or "bridge member interface" in server mode')
+
+
+ if openvpn['server_pool']:
+ if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']):
+ raise ConfigError('Server client-ip-pool requires both start and stop addresses in bridged mode')
+ else:
+ v4PoolStart = IPv4Address(openvpn['server_pool_start'])
+ v4PoolStop = IPv4Address(openvpn['server_pool_stop'])
+ if v4PoolStart > v4PoolStop:
+ raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}')
+ if (int(v4PoolStop) - int(v4PoolStart) >= 65536):
+ raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop}], maximum is 65536 addresses.')
+
+ v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop))
+ for client in openvpn['client']:
+ if client['ip']:
+ for v4PoolNet in v4PoolNets:
+ if IPv4Address(client['ip']) in v4PoolNet:
+ print(f'Warning: Client "{client["name"]}" IP {client["ip"]} is in server IP pool, it is not reserved for this client.',
+ file=stderr)
else:
# checks for both client and site-to-site go here
@@ -665,143 +752,98 @@ def verify(openvpn):
if not openvpn['auth_pass']:
raise ConfigError('Password for authentication is missing')
- #
- # Client
- #
- subnet = openvpn['server_subnet'].replace(' ', '/')
- for client in openvpn['client']:
- if client['ip'] and not ip_address(client['ip']) in ip_network(subnet):
- raise ConfigError('Client IP "{}" not in server subnet "{}'.format(client['ip'], subnet))
-
return None
def generate(openvpn):
if openvpn['deleted'] or openvpn['disable']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'openvpn')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
interface = openvpn['intf']
directory = os.path.dirname(get_config_name(interface))
- # we can't know which clients were deleted, remove all client configs
- if os.path.isdir(os.path.join(directory, 'ccd', interface)):
- rmtree(os.path.join(directory, 'ccd', interface), ignore_errors=True)
+ # we can't know in advance which clients have been,
+ # remove all client configs
+ ccd_dir = os.path.join(directory, 'ccd', interface)
+ if os.path.isdir(ccd_dir):
+ rmtree(ccd_dir, ignore_errors=True)
# create config directory on demand
- openvpn_mkdir(directory)
- # create status directory on demand
- openvpn_mkdir(directory + '/status')
- # create client config dir on demand
- openvpn_mkdir(directory + '/ccd')
- # crete client config dir per interface on demand
- openvpn_mkdir(directory + '/ccd/' + interface)
+ directories = []
+ directories.append(f'{directory}/status')
+ directories.append(f'{directory}/ccd/{interface}')
+ for onedir in directories:
+ if not os.path.exists(onedir):
+ os.makedirs(onedir, 0o755)
+ chown(onedir, user, group)
# Fix file permissons for keys
- fixup_permission(openvpn['shared_secret_file'])
- fixup_permission(openvpn['tls_key'])
+ fix_permissions = []
+ fix_permissions.append(openvpn['shared_secret_file'])
+ fix_permissions.append(openvpn['tls_key'])
# Generate User/Password authentication file
+ user_auth_file = f'/tmp/openvpn-{interface}-pw'
if openvpn['auth']:
- auth_file = '/tmp/openvpn-{}-pw'.format(interface)
- with open(auth_file, 'w') as f:
+ with open(user_auth_file, 'w') as f:
f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass']))
-
- fixup_permission(auth_file)
+ # also change permission on auth file
+ fix_permissions.append(user_auth_file)
else:
# delete old auth file if present
- if os.path.isfile('/tmp/openvpn-{}-pw'.format(interface)):
- os.remove('/tmp/openvpn-{}-pw'.format(interface))
-
- # get numeric uid/gid
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
+ if os.path.isfile(user_auth_file):
+ os.remove(user_auth_file)
# Generate client specific configuration
for client in openvpn['client']:
- client_file = directory + '/ccd/' + interface + '/' + client['name']
- tmpl = env.get_template('client.conf.tmpl')
- client_text = tmpl.render(client)
- with open(client_file, 'w') as f:
- f.write(client_text)
- os.chown(client_file, uid, gid)
-
- tmpl = env.get_template('server.conf.tmpl')
- config_text = tmpl.render(openvpn)
+ client_file = os.path.join(ccd_dir, client['name'])
+ render(client_file, 'openvpn/client.conf.tmpl', client)
+ chown(client_file, user, group)
+
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
- config_text = config_text.replace("&quot;",'"')
- with open(get_config_name(interface), 'w') as f:
- f.write(config_text)
- os.chown(get_config_name(interface), uid, gid)
+ render(get_config_name(interface), 'openvpn/server.conf.tmpl', openvpn,
+ formater=lambda _: _.replace("&quot;", '"'))
+ chown(get_config_name(interface), user, group)
+
+ # Fixup file permissions
+ for file in fix_permissions:
+ chmod_600(file)
return None
def apply(openvpn):
- pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf'])
-
- # Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the
- # service as the configuration is not re-read. Stop daemon only if it's
- # running - it could have died or killed by someone evil
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- cmd(command)
-
- # cleanup old PID file
- if os.path.isfile(pidfile):
- os.remove(pidfile)
+ interface = openvpn['intf']
+ call(f'systemctl stop openvpn@{interface}.service')
# Do some cleanup when OpenVPN is disabled/deleted
if openvpn['deleted'] or openvpn['disable']:
# cleanup old configuration file
- if os.path.isfile(get_config_name(openvpn['intf'])):
- os.remove(get_config_name(openvpn['intf']))
+ if os.path.isfile(get_config_name(interface)):
+ os.remove(get_config_name(interface))
# cleanup client config dir
- directory = os.path.dirname(get_config_name(openvpn['intf']))
- if os.path.isdir(os.path.join(directory, 'ccd', openvpn['intf'])):
- rmtree(os.path.join(directory, 'ccd', openvpn['intf']), ignore_errors=True)
-
- # cleanup auth file
- if os.path.isfile('/tmp/openvpn-{}-pw'.format(openvpn['intf'])):
- os.remove('/tmp/openvpn-{}-pw'.format(openvpn['intf']))
+ directory = os.path.dirname(get_config_name(interface))
+ ccd_dir = os.path.join(directory, 'ccd', interface)
+ if os.path.isdir(ccd_dir):
+ rmtree(ccd_dir, ignore_errors=True)
return None
# On configuration change we need to wait for the 'old' interface to
# vanish from the Kernel, if it is not gone, OpenVPN will report:
# ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
- while openvpn['intf'] in interfaces():
+ while interface in interfaces():
sleep(0.250) # 250ms
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- command += ' --exec /usr/sbin/openvpn'
- # now pass arguments to openvpn binary
- command += ' --'
- command += ' --daemon openvpn-' + openvpn['intf']
- command += ' --config ' + get_config_name(openvpn['intf'])
-
- # execute assembled command
- cmd(command)
+ call(f'systemctl start openvpn@{interface}.service')
# better late then sorry ... but we can only set interface alias after
# OpenVPN has been launched and created the interface
cnt = 0
- while openvpn['intf'] not in interfaces():
+ while interface not in interfaces():
# If VPN tunnel can't be established because the peer/server isn't
# (temporarily) available, the vtun interface never becomes registered
# with the kernel, and the commit would hang if there is no bail out
@@ -816,7 +858,7 @@ def apply(openvpn):
try:
# we need to catch the exception if the interface is not up due to
# reason stated above
- o = VTunIf(openvpn['intf'])
+ o = VTunIf(interface)
# update interface description used e.g. within SNMP
o.set_alias(openvpn['description'])
# IPv6 address autoconfiguration
@@ -834,7 +876,7 @@ def apply(openvpn):
# TAP interface needs to be brought up explicitly
if openvpn['type'] == 'tap':
if not openvpn['disable']:
- VTunIf(openvpn['intf']).set_admin_state('up')
+ VTunIf(interface).set_admin_state('up')
return None
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 407547175..f942b7d2f 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -18,14 +18,14 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import Interface
-from vyos.util import chown_file, chmod_x, cmd
+from vyos.util import chown, chmod_755, cmd
from vyos import ConfigError
+from vyos.template import render
+
default_config_data = {
'access_concentrator': '',
@@ -155,14 +155,12 @@ def verify(pppoe):
if vrf_name and vrf_name not in interfaces():
raise ConfigError(f'VRF {vrf_name} does not exist')
+ if pppoe['on_demand'] and pppoe['vrf']:
+ raise ConfigError('On-demand dialing and VRF can not be used at the same time')
+
return None
def generate(pppoe):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "pppoe")
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# set up configuration file path variables where our templates will be
# rendered into
intf = pppoe['intf']
@@ -192,40 +190,26 @@ def generate(pppoe):
else:
# Create PPP configuration files
- tmpl = env.get_template('peer.tmpl')
- config_text = tmpl.render(pppoe)
- with open(config_pppoe, 'w') as f:
- f.write(config_text)
-
+ render(config_pppoe, 'pppoe/peer.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-pre-up.d
- tmpl = env.get_template('ip-pre-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_pre_up, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-up.d
- tmpl = env.get_template('ip-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ip_up, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ip-down.d
- tmpl = env.get_template('ip-down.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ip_down, 'w') as f:
- f.write(config_text)
-
+ render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl',
+ pppoe, trim_blocks=True)
# Create script for ipv6-up.d
- tmpl = env.get_template('ipv6-up.script.tmpl')
- config_text = tmpl.render(pppoe)
- with open(script_pppoe_ipv6_up, 'w') as f:
- f.write(config_text)
+ render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl',
+ pppoe, trim_blocks=True)
# make generated script file executable
- chmod_x(script_pppoe_pre_up)
- chmod_x(script_pppoe_ip_up)
- chmod_x(script_pppoe_ip_down)
- chmod_x(script_pppoe_ipv6_up)
+ chmod_755(script_pppoe_pre_up)
+ chmod_755(script_pppoe_ip_up)
+ chmod_755(script_pppoe_ip_down)
+ chmod_755(script_pppoe_ipv6_up)
return None
@@ -240,7 +224,7 @@ def apply(pppoe):
cmd(f'systemctl start ppp@{intf}.service')
# make logfile owned by root / vyattacfg
- chown_file(pppoe['logfile'], 'root', 'vyattacfg')
+ chown(pppoe['logfile'], 'root', 'vyattacfg')
return None
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 50b5a12a0..655006146 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -51,8 +51,8 @@ default_config_data = {
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
'intf': '',
- 'link': '',
- 'link_changed': False,
+ 'source_interface': '',
+ 'source_interface_changed': False,
'mac': '',
'mode': 'private',
'vif_s': [],
@@ -166,12 +166,12 @@ def get_config():
if conf.exists('ipv6 dup-addr-detect-transmits'):
peth['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
- # Lower link device
- if conf.exists(['link']):
- peth['link'] = conf.return_value(['link'])
- tmp = conf.return_effective_value(['link'])
- if tmp != peth['link']:
- peth['link_changed'] = True
+ # Physical interface
+ if conf.exists(['source-interface']):
+ peth['source_interface'] = conf.return_value(['source-interface'])
+ tmp = conf.return_effective_value(['source-interface'])
+ if tmp != peth['source_interface']:
+ peth['source_interface_changed'] = True
# Media Access Control (MAC) address
if conf.exists(['mac']):
@@ -227,10 +227,10 @@ def verify(peth):
'is a member of bridge "{1}"!'.format(interface, bridge))
return None
- if not peth['link']:
+ if not peth['source_interface']:
raise ConfigError('Link device must be set for virtual ethernet {}'.format(peth['intf']))
- if not peth['link'] in interfaces():
+ if not peth['source_interface'] in interfaces():
raise ConfigError('Pseudo-ethernet source interface does not exist')
vrf_name = peth['vrf']
@@ -253,12 +253,12 @@ def apply(peth):
p.remove()
return None
- elif peth['link_changed']:
+ elif peth['source_interface_changed']:
# Check if MACVLAN interface already exists. Parameters like the
- # underlaying link device can not be changed on the fly and the
- # interface needs to be recreated from the bottom.
+ # underlaying source-interface device can not be changed on the fly
+ # and the interface needs to be recreated from the bottom.
#
- # link_changed also means - the interface was not present in the
+ # source_interface_changed also means - the interface was not present in the
# beginning and is newly created
if peth['intf'] in interfaces():
p = MACVLANIf(peth['intf'])
@@ -269,7 +269,7 @@ def apply(peth):
conf = deepcopy(MACVLANIf.get_config())
# Assign MACVLAN instance configuration parameters to config dict
- conf['link'] = peth['link']
+ conf['source_interface'] = peth['source_interface']
conf['mode'] = peth['mode']
# It is safe to "re-create" the interface always, there is a sanity check
@@ -281,32 +281,20 @@ def apply(peth):
# update interface description used e.g. within SNMP
p.set_alias(peth['description'])
- # get DHCP config dictionary and update values
- opt = p.get_dhcp_options()
-
if peth['dhcp_client_id']:
- opt['client_id'] = peth['dhcp_client_id']
+ p.dhcp.v4.options['client_id'] = peth['dhcp_client_id']
if peth['dhcp_hostname']:
- opt['hostname'] = peth['dhcp_hostname']
+ p.dhcp.v4.options['hostname'] = peth['dhcp_hostname']
if peth['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = peth['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- p.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = p.get_dhcpv6_options()
+ p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id']
if peth['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ p.dhcp.v6.options['dhcpv6_prm_only'] = True
if peth['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- p.set_dhcpv6_options(opt)
+ p.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
p.set_link_detect(peth['disable_link_detect'])
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 646e61c53..c51048aeb 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -26,21 +26,68 @@ from vyos.ifconfig.afi import IP4, IP6
from vyos.configdict import list_diff
from vyos.validate import is_ipv4, is_ipv6
from vyos import ConfigError
+from vyos.dicts import FixedDict
+class ConfigurationState(Config):
+ """
+ The current API require a dict to be generated by get_config()
+ which is then consumed by verify(), generate() and apply()
-class FixedDict(dict):
- def __init__ (self, **options):
- self._allowed = options.keys()
- super().__init__(**options)
+ ConfiguartionState is an helper class wrapping Config and providing
+ an common API to this dictionary structure
- def __setitem__ (self, k, v):
- if k not in self._allowed:
- raise ConfigError(f'Option "{k}" has no defined default')
- super().__setitem__(k, v)
+ Its to_dict() function return a dictionary containing three fields,
+ each a dict, called options, changes, actions.
+ options:
+
+ contains the configuration options for the dict and its value
+ {'options': {'commment': 'test'}} will be set if
+ 'set interface dummy dum1 description test' was used and
+ the key 'commment' is used to index the description info.
+
+ changes:
+
+ per key, let us know how the data was modified using one of the action
+ a special key called 'section' is used to indicate what happened to the
+ section. for example:
+
+ 'set interface dummy dum1 description test' when no interface was setup
+ will result in the following changes
+ {'changes': {'section': 'create', 'comment': 'create'}}
+
+ on an existing interface, depending if there was a description
+ 'set interface dummy dum1 description test' will result in one of
+ {'changes': {'comment': 'create'}} (not present before)
+ {'changes': {'comment': 'static'}} (unchanged)
+ {'changes': {'comment': 'modify'}} (changed from half)
+
+ and 'delete interface dummy dummy1 description' will result in:
+ {'changes': {'comment': 'delete'}}
+
+ actions:
+
+ for each action list the configuration key which were changes
+ in our example if we added the 'description' and added an IP we would have
+ {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}}
+
+ the actions are:
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ which for each field represent how it was modified since the last commit
+ """
-class ConfigurationState (Config):
def __init__ (self, section, default):
+ """
+ initialise the class for a given configuration path:
+
+ >>> conf = ConfigurationState('interfaces ethernet eth1')
+ all further references to get_value(s) and get_effective(s)
+ will be for this part of the configuration (eth1)
+ """
super().__init__()
self.section = section
self.default = deepcopy(default)
@@ -61,6 +108,15 @@ class ConfigurationState (Config):
self.changes['section'] = 'create'
def _act(self, section):
+ """
+ Returns for a given configuration field determine what happened to it
+
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ """
if self.exists(section):
if self.exists_effective(section):
if self.return_value(section) != self.return_effective_value(section):
@@ -89,24 +145,71 @@ class ConfigurationState (Config):
self.options[name] = value
def get_value(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_value
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_value)
def get_values(self, name, key, default=None):
+ """
+ >>> conf.get_values('addresses-add', 'address')
+ will place a list made of the IP present in 'interface dummy dum1 address'
+ into the dictionnary entry 'addr' using Config.return_values
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_values)
def get_effective(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_effective_value
+ (the data in the configuration to apply)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effective_value)
def get_effectives(self, name, key, default=None):
+ """
+ >>> conf.get_effectives('addresses-add', 'address')
+ will place a list made of the IP present in 'interface ethernet eth1 address'
+ into the dictionnary entry 'addresses-add' using Config.return_effectives_value
+ (the data in the un-modified configuration)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effectives_value)
def load(self, mapping):
+ """
+ load will take a dictionary defining how we wish the configuration
+ to be parsed and apply this definition to set the data.
+
+ >>> mapping = {
+ 'addresses-add' : ('address', True, None),
+ 'comment' : ('description', False, 'auto'),
+ }
+ >>> conf.load(mapping)
+
+ mapping is a dictionary where each key represents the name we wish
+ to have (such as 'addresses-add'), with a list a content representing
+ how the data should be parsed:
+ - the configuration section name
+ such as 'address' under 'interface ethernet eth1'
+ - boolean indicating if this data can have multiple values
+ for 'address', True, as multiple IPs can be set
+ for 'description', False, as it is a single string
+ - default represent the default value if absent from the configuration
+ 'None' indicate that no default should be set if the configuration
+ does not have the configuration section
+
+ """
for local_name, (config_name, multiple, default) in mapping.items():
if multiple:
self.get_values(local_name, config_name, default)
@@ -114,12 +217,21 @@ class ConfigurationState (Config):
self.get_value(local_name, config_name, default)
def remove_default (self,*options):
+ """
+ remove all the values which were not changed from the default
+ """
for option in options:
if self.exists(option) and self_return_value(option) != self.default[option]:
continue
del self.options[option]
def to_dict (self):
+ """
+ provide a dictionary with the generated data for the configuration
+ options: the configuration value for the key
+ changes: per key how they changed from the previous configuration
+ actions: per changes all the options which were changed
+ """
# as we have to use a dict() for the API for verify and apply the options
return {
'options': self.options,
@@ -203,7 +315,8 @@ def get_class (options):
}
kls = dispatch[options['type']]
- if options['type'] == 'gre' and not options['remote']:
+ if options['type'] == 'gre' and not options['remote'] \
+ and not options['key'] and not options['multicast']:
# will use GreTapIf on GreIf deletion but it does not matter
return GRETapIf
elif options['type'] == 'sit' and options['6rd-prefix']:
@@ -471,11 +584,17 @@ def apply(conf):
if changes['section'] in 'create' and option in tunnel.options:
# it was setup at creation
continue
+ if not options[option]:
+ # remote can be set to '' and it would generate an invalide command
+ continue
tunnel.set_interface(option, options[option])
# set other interface properties
for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast',
'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
+ if not options[option]:
+ # should never happen but better safe
+ continue
tunnel.set_interface(option, options[option])
# Configure interface address(es)
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index b9bfb242a..6639a9b0d 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -42,7 +42,8 @@ default_config_data = {
'ipv6_eui64_prefix': '',
'ipv6_forwarding': 1,
'ipv6_dup_addr_detect': 1,
- 'link': '',
+ 'source_address': '',
+ 'source_interface': '',
'mtu': 1450,
'remote': '',
'remote_port': 8472, # The Linux implementation of VXLAN pre-dates
@@ -124,9 +125,13 @@ def get_config():
if conf.exists('ipv6 dup-addr-detect-transmits'):
vxlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits'))
+ # VXLAN source address
+ if conf.exists('source-address'):
+ vxlan['source_address'] = conf.return_value('source-address')
+
# VXLAN underlay interface
- if conf.exists('link'):
- vxlan['link'] = conf.return_value('link')
+ if conf.exists('source-interface'):
+ vxlan['source_interface'] = conf.return_value('source-interface')
# Maximum Transmission Unit (MTU)
if conf.exists('mtu'):
@@ -162,18 +167,22 @@ def verify(vxlan):
print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
if vxlan['group']:
- if not vxlan['link']:
+ if not vxlan['source_interface']:
raise ConfigError('Multicast VXLAN requires an underlaying interface ')
- if not vxlan['link'] in interfaces():
+
+ if not vxlan['source_interface'] in interfaces():
raise ConfigError('VXLAN source interface does not exist')
+ if not (vxlan['group'] or vxlan['remote'] or vxlan['source_address']):
+ raise ConfigError('Group, remote or source-address must be configured')
+
if not vxlan['vni']:
raise ConfigError('Must configure VNI for VXLAN')
- if vxlan['link']:
+ if vxlan['source_interface']:
# VXLAN adds a 50 byte overhead - we need to check the underlaying MTU
# if our configured MTU is at least 50 bytes less
- underlay_mtu = int(Interface(vxlan['link']).get_mtu())
+ underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu())
if underlay_mtu < (vxlan['mtu'] + 50):
raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \
'MTU is to small ({})'.format(underlay_mtu))
@@ -202,7 +211,8 @@ def apply(vxlan):
# Assign VXLAN instance configuration parameters to config dict
conf['vni'] = vxlan['vni']
conf['group'] = vxlan['group']
- conf['dev'] = vxlan['link']
+ conf['src_address'] = vxlan['source_address']
+ conf['src_interface'] = vxlan['source_interface']
conf['remote'] = vxlan['remote']
conf['port'] = vxlan['remote_port']
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 54121a6c1..8bf81c747 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -14,173 +14,181 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import sys
import os
import re
+from sys import exit
from copy import deepcopy
from netifaces import interfaces
-from vyos import ConfigError
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.util import run, is_bridge_member
from vyos.ifconfig import WireGuardIf
+from vyos.util import chown, is_bridge_member, chmod_750
+from vyos.util import call
+from vyos import ConfigError
kdir = r'/config/auth/wireguard'
+default_config_data = {
+ 'intfc': '',
+ 'address': [],
+ 'address_remove': [],
+ 'description': '',
+ 'lport': None,
+ 'deleted': False,
+ 'disable': False,
+ 'fwmark': 0x00,
+ 'mtu': 1420,
+ 'peer': [],
+ 'peer_remove': [], # stores public keys of peers to remove
+ 'pk': f'{kdir}/default/private.key',
+ 'vrf': ''
+}
+
def _check_kmod():
- if not os.path.exists('/sys/module/wireguard'):
- if run('modprobe wireguard') != 0:
- raise ConfigError("modprobe wireguard failed")
+ modules = ['wireguard']
+ for module in modules:
+ if not os.path.exists(f'/sys/module/{module}'):
+ if call(f'modprobe {module}') != 0:
+ raise ConfigError(f'Loading Kernel module {module} failed')
def _migrate_default_keys():
if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'):
- old_umask = os.umask(0o027)
location = f'{kdir}/default'
- run(f'sudo mkdir -p {location}')
- run(f'sudo chgrp vyattacfg {location}')
- run(f'sudo chmod 750 {location}')
+ if not os.path.exists(location):
+ os.makedirs(location)
+
+ chown(location, 'root', 'vyattacfg')
+ chmod_750(location)
os.rename(f'{kdir}/private.key', f'{location}/private.key')
os.rename(f'{kdir}/public.key', f'{location}/public.key')
- os.umask(old_umask)
def get_config():
- c = Config()
- if not c.exists(['interfaces', 'wireguard']):
- return None
+ conf = Config()
+ base = ['interfaces', 'wireguard']
# determine tagNode instance
if 'VYOS_TAGNODE_VALUE' not in os.environ:
raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
- dflt_cnf = {
- 'intfc': '',
- 'addr': [],
- 'addr_remove': [],
- 'descr': '',
- 'lport': None,
- 'delete': False,
- 'state': 'up',
- 'fwmark': 0x00,
- 'mtu': 1420,
- 'peer': {},
- 'peer_remove': [],
- 'pk': '{}/default/private.key'.format(kdir)
- }
-
- ifname = str(os.environ['VYOS_TAGNODE_VALUE'])
- wg = deepcopy(dflt_cnf)
- wg['intfc'] = ifname
- wg['descr'] = ifname
-
- c.set_level(['interfaces', 'wireguard'])
-
- # interface removal state
- if not c.exists(ifname) and c.exists_effective(ifname):
- wg['delete'] = True
-
- if not wg['delete']:
- c.set_level(['interfaces', 'wireguard', ifname])
- if c.exists(['address']):
- wg['addr'] = c.return_values(['address'])
-
- # determine addresses which need to be removed
- eff_addr = c.return_effective_values(['address'])
- wg['addr_remove'] = list_diff(eff_addr, wg['addr'])
-
- # ifalias description
- if c.exists(['description']):
- wg['descr'] = c.return_value(['description'])
-
- # link state
- if c.exists(['disable']):
- wg['state'] = 'down'
-
- # local port to listen on
- if c.exists(['port']):
- wg['lport'] = c.return_value(['port'])
-
- # fwmark value
- if c.exists(['fwmark']):
- wg['fwmark'] = c.return_value(['fwmark'])
-
- # mtu
- if c.exists('mtu'):
- wg['mtu'] = c.return_value('mtu')
-
- # private key
- if c.exists(['private-key']):
- wg['pk'] = "{0}/{1}/private.key".format(
- kdir, c.return_value(['private-key']))
-
- # peer removal, wg identifies peers by its pubkey
- peer_eff = c.list_effective_nodes(['peer'])
- peer_rem = list_diff(peer_eff, c.list_nodes(['peer']))
- for p in peer_rem:
- wg['peer_remove'].append(
- c.return_effective_value(['peer', p, 'pubkey']))
-
- # peer settings
- if c.exists(['peer']):
- for p in c.list_nodes(['peer']):
- if not c.exists(['peer', p, 'disable']):
- wg['peer'].update(
- {
- p: {
- 'allowed-ips': [],
- 'address': '',
- 'port': '',
- 'pubkey': ''
- }
- }
- )
- # peer allowed-ips
- if c.exists(['peer', p, 'allowed-ips']):
- wg['peer'][p]['allowed-ips'] = c.return_values(
- ['peer', p, 'allowed-ips'])
- # peer address
- if c.exists(['peer', p, 'address']):
- wg['peer'][p]['address'] = c.return_value(
- ['peer', p, 'address'])
- # peer port
- if c.exists(['peer', p, 'port']):
- wg['peer'][p]['port'] = c.return_value(
- ['peer', p, 'port'])
- # persistent-keepalive
- if c.exists(['peer', p, 'persistent-keepalive']):
- wg['peer'][p]['persistent-keepalive'] = c.return_value(
- ['peer', p, 'persistent-keepalive'])
- # preshared-key
- if c.exists(['peer', p, 'preshared-key']):
- wg['peer'][p]['psk'] = c.return_value(
- ['peer', p, 'preshared-key'])
- # peer pubkeys
- key_eff = c.return_effective_value(['peer', p, 'pubkey'])
- key_cfg = c.return_value(['peer', p, 'pubkey'])
- wg['peer'][p]['pubkey'] = key_cfg
-
- # on a pubkey change we need to remove the pubkey first
- # peers are identified by pubkey, so key update means
- # peer removal and re-add
- if key_eff != key_cfg and key_eff != None:
- wg['peer_remove'].append(key_cfg)
-
- # if a peer is disabled, we have to exec a remove for it's pubkey
- else:
- peer_key = c.return_value(['peer', p, 'pubkey'])
- wg['peer_remove'].append(peer_key)
+ wg = deepcopy(default_config_data)
+ wg['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+
+ # Check if interface has been removed
+ if not conf.exists(base + [wg['intf']]):
+ wg['deleted'] = True
+ return wg
+
+ conf.set_level(base + [wg['intf']])
+
+ # retrieve configured interface addresses
+ if conf.exists(['address']):
+ wg['address'] = conf.return_values(['address'])
+
+ # get interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed
+ eff_addr = conf.return_effective_values(['address'])
+ wg['address_remove'] = list_diff(eff_addr, wg['address'])
+
+ # retrieve interface description
+ if conf.exists(['description']):
+ wg['description'] = conf.return_value(['description'])
+
+ # disable interface
+ if conf.exists(['disable']):
+ wg['disable'] = True
+
+ # local port to listen on
+ if conf.exists(['port']):
+ wg['lport'] = conf.return_value(['port'])
+
+ # fwmark value
+ if conf.exists(['fwmark']):
+ wg['fwmark'] = int(conf.return_value(['fwmark']))
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ wg['mtu'] = int(conf.return_value(['mtu']))
+
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ wg['vrf'] = conf.return_value('vrf')
+
+ # private key
+ if conf.exists(['private-key']):
+ wg['pk'] = "{0}/{1}/private.key".format(
+ kdir, conf.return_value(['private-key']))
+
+ # peer removal, wg identifies peers by its pubkey
+ peer_eff = conf.list_effective_nodes(['peer'])
+ peer_rem = list_diff(peer_eff, conf.list_nodes(['peer']))
+ for peer in peer_rem:
+ wg['peer_remove'].append(
+ conf.return_effective_value(['peer', peer, 'pubkey']))
+
+ # peer settings
+ if conf.exists(['peer']):
+ for p in conf.list_nodes(['peer']):
+ # set new config level for this peer
+ conf.set_level(base + [wg['intf'], 'peer', p])
+ peer = {
+ 'allowed-ips': [],
+ 'address': '',
+ 'name': p,
+ 'persistent_keepalive': '',
+ 'port': '',
+ 'psk': '',
+ 'pubkey': ''
+ }
+
+ # peer allowed-ips
+ if conf.exists(['allowed-ips']):
+ peer['allowed-ips'] = conf.return_values(['allowed-ips'])
+
+ # peer address
+ if conf.exists(['address']):
+ peer['address'] = conf.return_value(['address'])
+
+ # peer port
+ if conf.exists(['port']):
+ peer['port'] = conf.return_value(['port'])
+
+ # persistent-keepalive
+ if conf.exists(['persistent-keepalive']):
+ peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive'])
+
+ # preshared-key
+ if conf.exists(['preshared-key']):
+ peer['psk'] = conf.return_value(['preshared-key'])
+
+ # peer pubkeys
+ if conf.exists(['pubkey']):
+ key_eff = conf.return_effective_value(['pubkey'])
+ key_cfg = conf.return_value(['pubkey'])
+ peer['pubkey'] = key_cfg
+
+ # on a pubkey change we need to remove the pubkey first
+ # peers are identified by pubkey, so key update means
+ # peer removal and re-add
+ if key_eff != key_cfg and key_eff != None:
+ wg['peer_remove'].append(key_cfg)
+
+ # if a peer is disabled, we have to exec a remove for it's pubkey
+ if conf.exists(['disable']):
+ wg['peer_remove'].append(peer['pubkey'])
+ else:
+ wg['peer'].append(peer)
+
return wg
-def verify(c):
- if not c:
- return None
+def verify(wg):
+ interface = wg['intf']
- if c['delete']:
- interface = c['intfc']
+ if wg['deleted']:
is_member, bridge = is_bridge_member(interface)
if is_member:
# can not use a f'' formatted-string here as bridge would not get
@@ -189,98 +197,100 @@ def verify(c):
'is a member of bridge "{1}"!'.format(interface, bridge))
return None
- if not os.path.exists(c['pk']):
- raise ConfigError(
- "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'")
-
- if not c['delete']:
- if not c['addr']:
- raise ConfigError("ERROR: IP address required")
- if not c['peer']:
- raise ConfigError("ERROR: peer required")
- for p in c['peer']:
- if not c['peer'][p]['allowed-ips']:
- raise ConfigError("ERROR: allowed-ips required for peer " + p)
- if not c['peer'][p]['pubkey']:
- raise ConfigError("peer pubkey required for peer " + p)
-
-
-def apply(c):
- # no wg configs left, remove all interface from system
- # maybe move it into ifconfig.py
- if not c:
- net_devs = os.listdir('/sys/class/net/')
- for dev in net_devs:
- if os.path.isdir('/sys/class/net/' + dev):
- buf = open('/sys/class/net/' + dev + '/uevent', 'r').read()
- if re.search("DEVTYPE=wireguard", buf, re.I | re.M):
- wg_intf = re.sub("INTERFACE=", "", re.search(
- "INTERFACE=.*", buf, re.I | re.M).group(0))
- # XXX: we are ignoring any errors here
- run(f'ip l d dev {wg_intf} >/dev/null')
- return None
+ vrf_name = wg['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ if not os.path.exists(wg['pk']):
+ raise ConfigError('No keys found, generate them by executing:\n' \
+ '"run generate wireguard [keypair|named-keypairs]"')
+ if not wg['address']:
+ raise ConfigError(f'IP address required for interface "{interface}"!')
+
+ if not wg['peer']:
+ raise ConfigError(f'Peer required for interface "{interface}"!')
+
+ # run checks on individual configured WireGuard peer
+ for peer in wg['peer']:
+ peer_name = peer['name']
+ if not peer['allowed-ips']:
+ raise ConfigError(f'Peer allowed-ips required for peer "{peer_name}"!')
+
+ if not peer['pubkey']:
+ raise ConfigError(f'Peer public-key required for peer "{peer_name}"!')
+
+
+def apply(wg):
# init wg class
- intfc = WireGuardIf(c['intfc'])
+ w = WireGuardIf(wg['intf'])
# single interface removal
- if c['delete']:
- intfc.remove()
+ if wg['deleted']:
+ w.remove()
return None
- # remove IP addresses
- for ip in c['addr_remove']:
- intfc.del_addr(ip)
+ # Configure interface address(es)
+ # - not longer required addresses get removed first
+ # - newly addresses will be added second
+ for addr in wg['address_remove']:
+ w.del_addr(addr)
+ for addr in wg['address']:
+ w.add_addr(addr)
- # add IP addresses
- for ip in c['addr']:
- intfc.add_addr(ip)
+ # Maximum Transmission Unit (MTU)
+ w.set_mtu(wg['mtu'])
- # interface mtu
- intfc.set_mtu(int(c['mtu']))
+ # update interface description used e.g. within SNMP
+ w.set_alias(wg['description'])
- # ifalias for snmp from description
- intfc.set_alias(str(c['descr']))
+ # assign/remove VRF
+ w.set_vrf(wg['vrf'])
# remove peers
- if c['peer_remove']:
- for pkey in c['peer_remove']:
- intfc.remove_peer(pkey)
+ for pub_key in wg['peer_remove']:
+ w.remove_peer(pub_key)
# peer pubkey
# setting up the wg interface
- intfc.config['private-key'] = c['pk']
- for p in c['peer']:
+ w.config['private-key'] = c['pk']
+
+ for peer in wg['peer']:
# peer pubkey
- intfc.config['pubkey'] = str(c['peer'][p]['pubkey'])
+ w.config['pubkey'] = peer['pubkey']
# peer allowed-ips
- intfc.config['allowed-ips'] = c['peer'][p]['allowed-ips']
+ w.config['allowed-ips'] = peer['allowed-ips']
# local listen port
- if c['lport']:
- intfc.config['port'] = c['lport']
+ if wg['lport']:
+ w.config['port'] = wg['lport']
# fwmark
if c['fwmark']:
- intfc.config['fwmark'] = c['fwmark']
+ w.config['fwmark'] = wg['fwmark']
+
# endpoint
- if c['peer'][p]['address'] and c['peer'][p]['port']:
- intfc.config['endpoint'] = "{}:{}".format(c['peer'][p]['address'], c['peer'][p]['port'])
+ if peer['address'] and peer['port']:
+ w.config['endpoint'] = '{}:{}'.format(
+ peer['address'], peer['port'])
# persistent-keepalive
- if 'persistent-keepalive' in c['peer'][p]:
- intfc.config['keepalive'] = c['peer'][p]['persistent-keepalive']
+ if peer['persistent_keepalive']:
+ w.config['keepalive'] = peer['persistent_keepalive']
# maybe move it into ifconfig.py
# preshared-key - needs to be read from a file
- if 'psk' in c['peer'][p]:
+ if peer['psk']:
psk_file = '/config/auth/wireguard/psk'
- old_umask = os.umask(0o077)
- open(psk_file, 'w').write(str(c['peer'][p]['psk']))
- os.umask(old_umask)
- intfc.config['psk'] = psk_file
- intfc.update()
+ with open(psk_file, 'w') as f:
+ f.write(peer['psk'])
+ w.config['psk'] = psk_file
+
+ w.update()
- # interface state
- intfc.set_admin_state(c['state'])
+ # Enable/Disable interface
+ if wg['disable']:
+ w.set_admin_state('down')
+ else:
+ w.set_admin_state('up')
return None
@@ -293,4 +303,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- sys.exit(1)
+ exit(1)
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 709085b0f..498c24df0 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -19,21 +19,18 @@ from sys import exit
from re import findall
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from netaddr import EUI, mac_unix_expanded
from vyos.config import Config
from vyos.configdict import list_diff, vlan_to_dict
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import WiFiIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
-from vyos.util import process_running, chmod_x, chown_file, run, is_bridge_member
+from vyos.util import chown, is_bridge_member, call
from vyos import ConfigError
+from vyos.template import render
-user = 'root'
-group = 'vyattacfg'
default_config_data = {
'address': [],
@@ -115,43 +112,16 @@ default_config_data = {
}
def get_conf_file(conf_type, intf):
- cfg_dir = '/var/run/' + conf_type
+ cfg_dir = '/run/' + conf_type
# create directory on demand
if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
+ os.makedirs(cfg_dir, 0o755)
+ chown(cfg_dir, 'root', 'vyattacfg')
- cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
+ cfg_file = cfg_dir + r'/{}.conf'.format(intf)
return cfg_file
-def get_pid(conf_type, intf):
- cfg_dir = '/var/run/' + conf_type
-
- # create directory on demand
- if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
-
- cfg_file = cfg_dir + r'/{}.pid'.format(intf)
- return cfg_file
-
-
-def get_wpa_suppl_config_name(intf):
- cfg_dir = '/var/run/wpa_supplicant'
-
- # create directory on demand
- if not os.path.exists(cfg_dir):
- os.mkdir(cfg_dir)
- chmod_x(cfg_dir)
- chown_file(cfg_dir, user, group)
-
- cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
- return cfg_file
-
-
def get_config():
wifi = deepcopy(default_config_data)
conf = Config()
@@ -570,6 +540,9 @@ def verify(wifi):
if not wifi['phy']:
raise ConfigError('You must specify physical-device')
+ if not wifi['mode']:
+ raise ConfigError('You must specify a WiFi mode')
+
if wifi['op_mode'] == 'ap':
c = Config()
if not c.exists('system wifi-regulatory-domain'):
@@ -627,38 +600,20 @@ def verify(wifi):
return None
def generate(wifi):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir["data"], "templates", "wifi")
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ interface = wifi['intf']
# always stop hostapd service first before reconfiguring it
- pidfile = get_pid('hostapd', wifi['intf'])
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- run(command)
-
+ call(f'systemctl stop hostapd@{interface}.service')
# always stop wpa_supplicant service first before reconfiguring it
- pidfile = get_pid('wpa_supplicant', wifi['intf'])
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- run(command)
+ call(f'systemctl stop wpa_supplicant@{interface}.service')
# Delete config files if interface is removed
if wifi['deleted']:
- if os.path.isfile(get_conf_file('hostapd', wifi['intf'])):
- os.unlink(get_conf_file('hostapd', wifi['intf']))
+ if os.path.isfile(get_conf_file('hostapd', interface)):
+ os.unlink(get_conf_file('hostapd', interface))
- if os.path.isfile(get_conf_file('wpa_supplicant', wifi['intf'])):
- os.unlink(get_conf_file('wpa_supplicant', wifi['intf']))
+ if os.path.isfile(get_conf_file('wpa_supplicant', interface)):
+ os.unlink(get_conf_file('wpa_supplicant', interface))
return None
@@ -676,7 +631,7 @@ def generate(wifi):
tmp |= 0x020000000000
# we now need to add an offset to our MAC address indicating this
# subinterfaces index
- tmp += int(findall(r'\d+', wifi['intf'])[0])
+ tmp += int(findall(r'\d+', interface)[0])
# convert integer to "real" MAC address representation
mac = EUI(hex(tmp).split('x')[-1])
@@ -686,22 +641,19 @@ def generate(wifi):
# render appropriate new config files depending on access-point or station mode
if wifi['op_mode'] == 'ap':
- tmpl = env.get_template('hostapd.conf.tmpl')
- config_text = tmpl.render(wifi)
- with open(get_conf_file('hostapd', wifi['intf']), 'w') as f:
- f.write(config_text)
+ conf = get_conf_file('hostapd', interface)
+ render(conf, 'wifi/hostapd.conf.tmpl', wifi)
elif wifi['op_mode'] == 'station':
- tmpl = env.get_template('wpa_supplicant.conf.tmpl')
- config_text = tmpl.render(wifi)
- with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f:
- f.write(config_text)
+ conf = get_conf_file('wpa_supplicant', interface)
+ render(conf, 'wifi/wpa_supplicant.conf.tmpl', wifi)
return None
def apply(wifi):
+ interface = wifi['intf']
if wifi['deleted']:
- w = WiFiIf(wifi['intf'])
+ w = WiFiIf(interface)
# delete interface
w.remove()
else:
@@ -714,7 +666,7 @@ def apply(wifi):
conf['phy'] = wifi['phy']
# Finally create the new interface
- w = WiFiIf(wifi['intf'], **conf)
+ w = WiFiIf(interface, **conf)
# assign/remove VRF
w.set_vrf(wifi['vrf'])
@@ -722,32 +674,20 @@ def apply(wifi):
# update interface description used e.g. within SNMP
w.set_alias(wifi['description'])
- # get DHCP config dictionary and update values
- opt = w.get_dhcp_options()
-
if wifi['dhcp_client_id']:
- opt['client_id'] = wifi['dhcp_client_id']
+ w.dhcp.v4.options['client_id'] = wifi['dhcp_client_id']
if wifi['dhcp_hostname']:
- opt['hostname'] = wifi['dhcp_hostname']
+ w.dhcp.v4.options['hostname'] = wifi['dhcp_hostname']
if wifi['dhcp_vendor_class_id']:
- opt['vendor_class_id'] = wifi['dhcp_vendor_class_id']
-
- # store DHCP config dictionary - used later on when addresses are aquired
- w.set_dhcp_options(opt)
-
- # get DHCPv6 config dictionary and update values
- opt = w.get_dhcpv6_options()
+ w.dhcp.v4.options['vendor_class_id'] = wifi['dhcp_vendor_class_id']
if wifi['dhcpv6_prm_only']:
- opt['dhcpv6_prm_only'] = True
+ w.dhcp.v6.options['dhcpv6_prm_only'] = True
if wifi['dhcpv6_temporary']:
- opt['dhcpv6_temporary'] = True
-
- # store DHCPv6 config dictionary - used later on when addresses are aquired
- w.set_dhcpv6_options(opt)
+ w.dhcp.v6.options['dhcpv6_temporary'] = True
# ignore link state changes
w.set_link_detect(wifi['disable_link_detect'])
@@ -786,7 +726,7 @@ def apply(wifi):
# remove no longer required VLAN interfaces (vif)
for vif in wifi['vif_remove']:
- e.del_vlan(vif)
+ w.del_vlan(vif)
# create VLAN interfaces (vif)
for vif in wifi['vif']:
@@ -796,11 +736,11 @@ def apply(wifi):
try:
# on system bootup the above condition is true but the interface
# does not exists, which throws an exception, but that's legal
- e.del_vlan(vif['id'])
+ w.del_vlan(vif['id'])
except:
pass
- vlan = e.add_vlan(vif['id'])
+ vlan = w.add_vlan(vif['id'])
apply_vlan_config(vlan, vif)
# Enable/Disable interface - interface is always placed in
@@ -811,38 +751,10 @@ def apply(wifi):
# Physical interface is now configured. Proceed by starting hostapd or
# wpa_supplicant daemon. When type is monitor we can just skip this.
if wifi['op_mode'] == 'ap':
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + get_pid('hostapd', wifi['intf'])
- command += ' --exec /usr/sbin/hostapd'
- # now pass arguments to hostapd binary
- command += ' -- '
- command += ' -B'
- command += ' -P ' + get_pid('hostapd', wifi['intf'])
- command += ' ' + get_conf_file('hostapd', wifi['intf'])
-
- # execute assembled command
- run(command)
+ call(f'systemctl start hostapd@{interface}.service')
elif wifi['op_mode'] == 'station':
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + get_pid('hostapd', wifi['intf'])
- command += ' --exec /sbin/wpa_supplicant'
- # now pass arguments to hostapd binary
- command += ' -- '
- command += ' -s -B -D nl80211'
- command += ' -P ' + get_pid('wpa_supplicant', wifi['intf'])
- command += ' -i ' + wifi['intf']
- command += ' -c ' + \
- get_conf_file('wpa_supplicant', wifi['intf'])
-
- # execute assembled command
- run(command)
+ call(f'systemctl start wpa_supplicant@{interface}.service')
return None
diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py
index 49445aaa4..da1855cd9 100755
--- a/src/conf_mode/interfaces-wirelessmodem.py
+++ b/src/conf_mode/interfaces-wirelessmodem.py
@@ -18,13 +18,17 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import interfaces
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import chown_file, chmod_x, cmd, run, is_bridge_member
+from vyos.util import chown
+from vyos.util import chmod_755
+from vyos.util import is_bridge_member
+from vyos.util import cmd
+from vyos.util import call
from vyos import ConfigError
+from vyos.template import render
+
default_config_data = {
'address': [],
@@ -48,7 +52,7 @@ def check_kmod():
modules = ['option', 'usb_wwan', 'usbserial']
for module in modules:
if not os.path.exists(f'/sys/module/{module}'):
- if run(f'modprobe {module}') != 0:
+ if call(f'modprobe {module}') != 0:
raise ConfigError(f'Loading Kernel module {module} failed')
def get_config():
@@ -139,11 +143,6 @@ def verify(wwan):
return None
def generate(wwan):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wwan')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# set up configuration file path variables where our templates will be
# rendered into
intf = wwan['intf']
@@ -173,39 +172,20 @@ def generate(wwan):
else:
# Create PPP configuration files
- tmpl = env.get_template('peer.tmpl')
- config_text = tmpl.render(wwan)
- with open(config_wwan, 'w') as f:
- f.write(config_text)
-
+ render(config_wwan, 'wwan/peer.tmpl', wwan)
# Create PPP chat script
- tmpl = env.get_template('chat.tmpl')
- config_text = tmpl.render(wwan)
- with open(config_wwan_chat, 'w') as f:
- f.write(config_text)
-
+ render(config_wwan_chat, 'wwan/chat.tmpl', wwan)
# Create script for ip-pre-up.d
- tmpl = env.get_template('ip-pre-up.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_pre_up, 'w') as f:
- f.write(config_text)
-
+ render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', wwan)
# Create script for ip-up.d
- tmpl = env.get_template('ip-up.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_ip_up, 'w') as f:
- f.write(config_text)
-
+ render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', wwan)
# Create script for ip-down.d
- tmpl = env.get_template('ip-down.script.tmpl')
- config_text = tmpl.render(wwan)
- with open(script_wwan_ip_down, 'w') as f:
- f.write(config_text)
+ render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', wwan)
# make generated script file executable
- chmod_x(script_wwan_pre_up)
- chmod_x(script_wwan_ip_up)
- chmod_x(script_wwan_ip_down)
+ chmod_755(script_wwan_pre_up)
+ chmod_755(script_wwan_ip_up)
+ chmod_755(script_wwan_ip_down)
return None
@@ -219,7 +199,7 @@ def apply(wwan):
intf = wwan['intf']
cmd(f'systemctl start ppp@{intf}.service')
# make logfile owned by root / vyattacfg
- chown_file(wwan['logfile'], 'root', 'vyattacfg')
+ chown(wwan['logfile'], 'root', 'vyattacfg')
return None
diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py
index c2f5c8e07..4fffa11ee 100755
--- a/src/conf_mode/ipsec-settings.py
+++ b/src/conf_mode/ipsec-settings.py
@@ -18,13 +18,13 @@ import re
import os
from time import sleep
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
+
ra_conn_name = "remote-access"
charon_conf_file = "/etc/strongswan.d/charon.conf"
@@ -99,7 +99,7 @@ def get_config():
### Remove config from file by delimiter
def remove_confs(delim_begin, delim_end, conf_file):
- run("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file)
+ call("sed -i '/"+delim_begin+"/,/"+delim_end+"/d' "+conf_file)
### Checking certificate storage and notice if certificate not in /config directory
@@ -112,7 +112,7 @@ def check_cert_file_store(cert_name, file_path, dts_path):
else:
### Cpy file to /etc/ipsec.d/certs/ /etc/ipsec.d/cacerts/
# todo make check
- ret = run('cp -f '+file_path+' '+dts_path)
+ ret = call('cp -f '+file_path+' '+dts_path)
if ret:
raise ConfigError("L2TP VPN configuration error: Cannot copy "+file_path)
@@ -147,43 +147,26 @@ def verify(data):
raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.")
def generate(data):
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipsec')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('charon.tmpl')
- config_text = tmpl.render(data)
- with open(charon_conf_file, 'w') as f:
- f.write(config_text)
+ render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True)
if data["ipsec_l2tp"]:
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
- tmpl = env.get_template('ipsec.secrets.tmpl')
- l2pt_ipsec_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(ipsec_secrets_flie,'w') as f:
- f.write(l2pt_ipsec_secrets_txt)
+ render(ipsec_secrets_flie, 'ipsec/ipsec.secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
- tmpl = env.get_template('remote-access.tmpl')
- ipsec_ra_conn_txt = tmpl.render(c)
old_umask = os.umask(0o077)
# Create tunnels directory if does not exist
if not os.path.exists(ipsec_ra_conn_dir):
os.makedirs(ipsec_ra_conn_dir)
- with open(ipsec_ra_conn_file,'w') as f:
- f.write(ipsec_ra_conn_txt)
+ render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', c, trim_blocks=True)
os.umask(old_umask)
-
- tmpl = env.get_template('ipsec.conf.tmpl')
- l2pt_ipsec_conf_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(ipsec_conf_flie,'a') as f:
- f.write(l2pt_ipsec_conf_txt)
+ render(ipsec_conf_flie, 'ipsec/ipsec.conf.tmpl', c, trim_blocks=True)
os.umask(old_umask)
else:
@@ -193,12 +176,12 @@ def generate(data):
remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_flie)
def restart_ipsec():
- run('ipsec restart >&/dev/null')
+ call('ipsec restart >&/dev/null')
# counter for apply swanctl config
counter = 10
while counter <= 10:
if os.path.exists(charon_pidfile):
- run('swanctl -q >&/dev/null')
+ call('swanctl -q >&/dev/null')
break
counter -=1
sleep(1)
diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py
index a4dbecbaa..2db31d3fc 100755
--- a/src/conf_mode/le_cert.py
+++ b/src/conf_mode/le_cert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-2020 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,8 +13,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-#
import sys
import os
@@ -22,8 +20,8 @@ import os
import vyos.defaults
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import cmd, run
-
+from vyos.util import cmd
+from vyos.util import call
vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode']
@@ -85,17 +83,17 @@ def generate(cert):
# certbot will attempt to reload nginx, even with 'certonly';
# start nginx if not active
- ret = run('systemctl is-active --quiet nginx.ervice')
+ ret = call('systemctl is-active --quiet nginx.service')
if ret:
- run('sudo systemctl start nginx.service')
+ call('systemctl start nginx.service')
request_certbot(cert)
def apply(cert):
if cert is not None:
- run('sudo systemctl restart certbot.timer')
+ call('systemctl restart certbot.timer')
else:
- run('sudo systemctl stop certbot.timer')
+ call('systemctl stop certbot.timer')
return None
for dep in dependencies:
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index c090bba83..d128c1fe6 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -18,15 +18,14 @@ import os
import re
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
from vyos.validate import is_addr_assigned,is_loopback_addr
-from vyos.defaults import directories as vyos_data_dir
from vyos.version import get_version_data
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = "/etc/default/lldpd"
@@ -210,11 +209,6 @@ def generate(lldp):
if lldp is None:
return
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'lldp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# generate listen on interfaces
for intf in lldp['interface_list']:
tmp = ''
@@ -226,25 +220,18 @@ def generate(lldp):
lldp['options']['listen_on'].append(tmp)
# generate /etc/default/lldpd
- tmpl = env.get_template('lldpd.tmpl')
- config_text = tmpl.render(lldp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'lldp/lldpd.tmpl', lldp)
# generate /etc/lldpd.d/01-vyos.conf
- tmpl = env.get_template('vyos.conf.tmpl')
- config_text = tmpl.render(lldp)
- with open(vyos_config_file, 'w') as f:
- f.write(config_text)
+ render(vyos_config_file, 'lldp/vyos.conf.tmpl', lldp)
def apply(lldp):
if lldp:
# start/restart lldp service
- run('sudo systemctl restart lldpd.service')
+ call('sudo systemctl restart lldpd.service')
else:
# LLDP service has been terminated
- run('sudo systemctl stop lldpd.service')
+ call('sudo systemctl stop lldpd.service')
os.unlink(config_file)
os.unlink(vyos_config_file)
diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py
index 2bccd9153..a652553f7 100755
--- a/src/conf_mode/mdns_repeater.py
+++ b/src/conf_mode/mdns_repeater.py
@@ -18,14 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from netifaces import ifaddresses, AF_INET
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
-
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/mdns-repeater'
@@ -82,25 +80,16 @@ def generate(mdns):
print('Warning: mDNS repeater will be deactivated because it is disabled')
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mdns-repeater')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('mdns-repeater.tmpl')
- config_text = tmpl.render(mdns)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns)
return None
def apply(mdns):
if (mdns is None) or mdns['disabled']:
- run('sudo systemctl stop mdns-repeater')
+ call('systemctl stop mdns-repeater.service')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- run('sudo systemctl restart mdns-repeater')
+ call('systemctl restart mdns-repeater.service')
return None
diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py
index 998022a8c..6d32f7fd6 100755
--- a/src/conf_mode/ntp.py
+++ b/src/conf_mode/ntp.py
@@ -18,14 +18,12 @@ import os
from copy import deepcopy
from ipaddress import ip_network
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
+from vyos.util import call
+from vyos.template import render
from vyos import ConfigError
-from vyos.util import run
-
config_file = r'/etc/ntp.conf'
@@ -100,24 +98,15 @@ def generate(ntp):
if ntp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ntp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('ntp.conf.tmpl')
- config_text = tmpl.render(ntp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'ntp/ntp.conf.tmpl', ntp)
return None
def apply(ntp):
if ntp is not None:
- run('sudo systemctl restart ntp.service')
+ call('sudo systemctl restart ntp.service')
else:
# NTP support is removed in the commit
- run('sudo systemctl stop ntp.service')
+ call('sudo systemctl stop ntp.service')
os.unlink(config_file)
return None
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index a62d2158e..ed8c3637b 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -18,13 +18,12 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv6_link_local, is_ipv6
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/bfd.frr'
@@ -191,23 +190,14 @@ def generate(bfd):
if bfd is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'frr-bfd')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('bfd.frr.tmpl')
- config_text = tmpl.render(bfd)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'frr-bfd/bfd.frr.tmpl', bfd)
return None
def apply(bfd):
if bfd is None:
return None
- run("vtysh -d bfdd -f " + config_file)
+ call("vtysh -d bfdd -f " + config_file)
if os.path.exists(config_file):
os.remove(config_file)
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index 6e819a15a..9b338c5b9 100755
--- a/src/conf_mode/protocols_igmp.py
+++ b/src/conf_mode/protocols_igmp.py
@@ -17,13 +17,12 @@
import os
from ipaddress import IPv4Address
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos import ConfigError
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/igmp.frr'
@@ -88,16 +87,7 @@ def generate(igmp):
if igmp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'igmp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('igmp.frr.tmpl')
- config_text = tmpl.render(igmp)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'igmp/igmp.frr.tmpl', igmp)
return None
def apply(igmp):
@@ -105,7 +95,7 @@ def apply(igmp):
return None
if os.path.exists(config_file):
- run("sudo vtysh -d pimd -f " + config_file)
+ call("sudo vtysh -d pimd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index 6e5d08397..0a241277d 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -16,18 +16,16 @@
import os
-from jinja2 import FileSystemLoader, Environment
-
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/ldpd.frr'
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
conf = Config()
@@ -129,16 +127,7 @@ def generate(mpls):
if mpls is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'mpls')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('ldpd.frr.tmpl')
- config_text = tmpl.render(mpls)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'mpls/ldpd.frr.tmpl', mpls)
return None
def apply(mpls):
@@ -162,7 +151,7 @@ def apply(mpls):
operate_mpls_on_intfc(diactive_ifaces, 0)
if os.path.exists(config_file):
- run("sudo vtysh -d ldpd -f " + config_file)
+ call("sudo vtysh -d ldpd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 9b74fe992..f12de4a72 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -17,13 +17,12 @@
import os
from ipaddress import IPv4Address
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/tmp/pimd.frr'
@@ -115,16 +114,7 @@ def generate(pim):
if pim is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pim')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('pimd.frr.tmpl')
- config_text = tmpl.render(pim)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'pim/pimd.frr.tmpl', pim)
return None
def apply(pim):
@@ -132,7 +122,7 @@ def apply(pim):
return None
if os.path.exists(config_file):
- run("vtysh -d pimd -f " + config_file)
+ call("vtysh -d pimd -f " + config_file)
os.remove(config_file)
return None
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index bd1d44bc8..236480854 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -17,16 +17,15 @@
import os
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from pwd import getpwnam
from socket import gethostname
from sys import exit
from urllib3 import PoolManager
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/salt/minion'
@@ -88,18 +87,10 @@ def generate(salt):
if salt is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'salt-minion')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
if not os.path.exists(directory):
os.makedirs(directory)
- tmpl = env.get_template('minion.tmpl')
- config_text = tmpl.render(salt)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'salt-minion/minion.tmpl', salt)
path = "/etc/salt/"
for path in paths:
@@ -126,10 +117,10 @@ def generate(salt):
def apply(salt):
if salt is not None:
- run("sudo systemctl restart salt-minion")
+ call("sudo systemctl restart salt-minion")
else:
# Salt access is removed in the commit
- run("sudo systemctl stop salt-minion")
+ call("sudo systemctl stop salt-minion")
os.unlink(config_file)
return None
diff --git a/src/conf_mode/service-ipoe.py b/src/conf_mode/service-ipoe.py
index 5bd4aea2e..3a14d92ef 100755
--- a/src/conf_mode/service-ipoe.py
+++ b/src/conf_mode/service-ipoe.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
ipoe_cnf_dir = r'/etc/accel-ppp/ipoe'
ipoe_cnf = ipoe_cnf_dir + r'/ipoe.config'
@@ -219,25 +219,15 @@ def generate(c):
if c == None or not c:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ipoe-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
c['thread_cnt'] = _get_cpu()
if c['auth']['mech'] == 'local':
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'ipoe-server/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
- tmpl = env.get_template('ipoe.config.tmpl')
- config_text = tmpl.render(c)
- with open(ipoe_cnf, 'w') as f:
- f.write(config_text)
+ render(ipoe_cnf, 'ipoe-server/ipoe.config.tmpl', c, trim_blocks=True)
+ # return c ??
return c
diff --git a/src/conf_mode/service-pppoe.py b/src/conf_mode/service-pppoe.py
index d3fc82406..a96249199 100755
--- a/src/conf_mode/service-pppoe.py
+++ b/src/conf_mode/service-pppoe.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
pidfile = r'/var/run/accel_pppoe.pid'
pppoe_cnf_dir = r'/etc/accel-ppp/pppoe'
@@ -376,11 +376,6 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pppoe-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
# accel-cmd reload doesn't work so any change results in a restart of the
# daemon
try:
@@ -394,17 +389,11 @@ def generate(c):
else:
c['thread_cnt'] = int(os.cpu_count() / 2)
- tmpl = env.get_template('pppoe.config.tmpl')
- config_text = tmpl.render(c)
- with open(pppoe_conf, 'w') as f:
- f.write(config_text)
+ render(pppoe_conf, 'pppoe-server/pppoe.config.tmpl', c, trim_blocks=True)
if c['authentication']['local-users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'pppoe-server/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
return c
diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py
index 0173b7242..620f3eacf 100755
--- a/src/conf_mode/service-router-advert.py
+++ b/src/conf_mode/service-router-advert.py
@@ -16,14 +16,13 @@
import os
-from jinja2 import FileSystemLoader, Environment
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/radvd.conf'
@@ -139,15 +138,7 @@ def generate(rtradv):
if not rtradv['interfaces']:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'router-advert')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('radvd.conf.tmpl')
- config_text = tmpl.render(rtradv)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True)
# adjust file permissions of new configuration file
if os.path.exists(config_file):
@@ -158,13 +149,13 @@ def generate(rtradv):
def apply(rtradv):
if not rtradv['interfaces']:
# bail out early - looks like removal from running config
- run('systemctl stop radvd.service')
+ call('systemctl stop radvd.service')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- run('systemctl restart radvd.service')
+ call('systemctl restart radvd.service')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 414236c88..d654dcb84 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -20,14 +20,13 @@ from binascii import hexlify
from time import sleep
from stat import S_IRWXU, S_IXGRP, S_IXOTH, S_IROTH, S_IRGRP
from sys import exit
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv4, is_addr_assigned
from vyos.version import get_version_data
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file_client = r'/etc/snmp/snmp.conf'
@@ -509,7 +508,7 @@ def generate(snmp):
#
# As we are manipulating the snmpd user database we have to stop it first!
# This is even save if service is going to be removed
- run('systemctl stop snmpd.service')
+ call('systemctl stop snmpd.service')
config_files = [config_file_client, config_file_daemon, config_file_access,
config_file_user]
for file in config_files:
@@ -518,34 +517,14 @@ def generate(snmp):
if snmp is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'snmp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
# Write client config file
- tmpl = env.get_template('etc.snmp.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_client, 'w') as f:
- f.write(config_text)
-
+ render(config_file_client, 'snmp/etc.snmp.conf.tmpl', snmp)
# Write server config file
- tmpl = env.get_template('etc.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_daemon, 'w') as f:
- f.write(config_text)
-
+ render(config_file_daemon, 'snmp/etc.snmpd.conf.tmpl', snmp)
# Write access rights config file
- tmpl = env.get_template('usr.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_access, 'w') as f:
- f.write(config_text)
-
+ render(config_file_access, 'snmp/usr.snmpd.conf.tmpl', snmp)
# Write access rights config file
- tmpl = env.get_template('var.snmpd.conf.tmpl')
- config_text = tmpl.render(snmp)
- with open(config_file_user, 'w') as f:
- f.write(config_text)
+ render(config_file_user, 'snmp/var.snmpd.conf.tmpl', snmp)
return None
@@ -554,7 +533,7 @@ def apply(snmp):
return None
# start SNMP daemon
- run("systemctl restart snmpd.service")
+ call("systemctl restart snmpd.service")
# Passwords are not available immediately in the configuration file,
# after daemon startup - we wait until they have been processed by
@@ -595,15 +574,15 @@ def apply(snmp):
# Now update the running configuration
#
- # Currently when executing run() the environment does not
+ # Currently when executing call() the environment does not
# have the vyos_libexec_dir variable set, see Phabricator T685.
- run('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['auth_pw']))
- run('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['priv_pw']))
- run('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
- run('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" auth encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['auth_pw']))
+ call('/opt/vyatta/sbin/my_set service snmp v3 user "{0}" privacy encrypted-key "{1}" > /dev/null'.format(cfg['user'], cfg['priv_pw']))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" auth plaintext-key > /dev/null'.format(cfg['user']))
+ call('/opt/vyatta/sbin/my_delete service snmp v3 user "{0}" privacy plaintext-key > /dev/null'.format(cfg['user']))
# Enable AgentX in FRR
- run('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+ call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
return None
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index a85dcd7f2..ae79eac2d 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -15,13 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/ssh/sshd_config'
@@ -120,23 +119,15 @@ def generate(ssh):
if ssh is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'ssh')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('sshd_config.tmpl')
- config_text = tmpl.render(ssh)
- with open(config_file, 'w') as f:
- f.write(config_text)
+ render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True)
return None
def apply(ssh):
if ssh is not None and 'port' in ssh.keys():
- run("sudo systemctl restart ssh.service")
+ call("sudo systemctl restart ssh.service")
else:
# SSH access is removed in the commit
- run("sudo systemctl stop ssh.service")
+ call("sudo systemctl stop ssh.service")
if os.path.isfile(config_file):
os.unlink(config_file)
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index 66f563939..8a1ac8411 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -20,7 +20,7 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -31,7 +31,7 @@ default_config_data = {
}
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
ip_opt = deepcopy(default_config_data)
diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py
index 4e3de6fe9..04a063564 100755
--- a/src/conf_mode/system-ipv6.py
+++ b/src/conf_mode/system-ipv6.py
@@ -21,7 +21,7 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
ipv6_disable_file = '/etc/modprobe.d/vyos_disable_ipv6.conf'
@@ -37,7 +37,7 @@ default_config_data = {
}
def sysctl(name, value):
- run('sysctl -wq {}={}'.format(name, value))
+ call('sysctl -wq {}={}'.format(name, value))
def get_config():
ip_opt = deepcopy(default_config_data)
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 7c99fce39..6008ca0b3 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -16,7 +16,6 @@
import os
-from jinja2 import FileSystemLoader, Environment
from psutil import users
from pwd import getpwall, getpwnam
from stat import S_IRUSR, S_IWUSR, S_IRWXU, S_IRGRP, S_IXGRP
@@ -24,9 +23,12 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.template import render
+
radius_config_file = "/etc/pam_radius_auth.conf"
@@ -207,19 +209,19 @@ def generate(login):
# remove old plaintext password
# and set new encrypted password
- run("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name']))
- run("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted']))
+ os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name']))
+ os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted']))
- if len(login['radius_server']) > 0:
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'system-login')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
+ # env = os.environ.copy()
+ # env['vyos_libexec_dir'] = '/usr/libexec/vyos'
- tmpl = env.get_template('pam_radius_auth.conf.tmpl')
- config_text = tmpl.render(login)
- with open(radius_config_file, 'w') as f:
- f.write(config_text)
+ # call("/opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password ''".format(user['name']),
+ # env=env)
+ # call("/opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}'".format(user['name'], user['password_encrypted']),
+ # env=env)
+
+ if len(login['radius_server']) > 0:
+ render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', login)
uid = getpwnam('root').pw_uid
gid = getpwnam('root').pw_gid
@@ -255,7 +257,7 @@ def apply(login):
command += " {}".format(user['name'])
try:
- run(command)
+ cmd(command)
uid = getpwnam(user['name']).pw_uid
gid = getpwnam(user['name']).pw_gid
@@ -295,10 +297,10 @@ def apply(login):
# Logout user if he is logged in
if user in list(set([tmp[0] for tmp in users()])):
print('{} is logged in, forcing logout'.format(user))
- run('pkill -HUP -u {}'.format(user))
+ call('pkill -HUP -u {}'.format(user))
# Remove user account but leave home directory to be safe
- run('userdel -r {} 2>/dev/null'.format(user))
+ call(f'userdel -r {user}', stderr=DEVNULL)
except Exception as e:
raise ConfigError('Deleting user "{}" raised an exception: {}'.format(user, e))
@@ -308,8 +310,10 @@ def apply(login):
#
if len(login['radius_server']) > 0:
try:
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
# Enable RADIUS in PAM
- run("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius")
+ cmd("pam-auth-update --package --enable radius", env=env)
# Make NSS system aware of RADIUS, too
command = "sed -i -e \'/\smapname/b\' \
@@ -320,15 +324,18 @@ def apply(login):
-e \'/^group:[^#]*$/s/: */&mapname /\' \
/etc/nsswitch.conf"
- run(command)
+ cmd(command)
except Exception as e:
raise ConfigError('RADIUS configuration failed: {}'.format(e))
else:
try:
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
+
# Disable RADIUS in PAM
- run("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius")
+ cmd("pam-auth-update --package --remove radius", env=env)
command = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \
-e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \
@@ -336,10 +343,10 @@ def apply(login):
-e \'s/[ \t]*$//\' \
/etc/nsswitch.conf"
- run(command)
+ cmd(command)
except Exception as e:
- raise ConfigError('Removing RADIUS configuration failed'.format(e))
+ raise ConfigError('Removing RADIUS configuration failed.\n{}'.format(e))
return None
diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py
index 063a82463..b3dbc82fb 100755
--- a/src/conf_mode/system-options.py
+++ b/src/conf_mode/system-options.py
@@ -52,9 +52,9 @@ def generate(opt):
def apply(opt):
# Beep action
if opt['beep_if_fully_booted']:
- run('systemctl enable vyos-beep.service >/dev/null 2>&1')
+ run('systemctl enable vyos-beep.service')
else:
- run('systemctl disable vyos-beep.service >/dev/null 2>&1')
+ run('systemctl disable vyos-beep.service')
# Ctrl-Alt-Delete action
if opt['ctrl_alt_del'] == 'ignore':
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 25b9b5bed..9da3d9157 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -17,13 +17,13 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
def get_config():
c = Config()
@@ -192,22 +192,13 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'syslog')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
- tmpl = env.get_template('rsyslog.conf.tmpl')
- config_text = tmpl.render(c)
- with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f:
- f.write(config_text)
+ conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
+ render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True)
# eventually write for each file its own logrotate file, since size is
# defined it shouldn't matter
- tmpl = env.get_template('logrotate.tmpl')
- config_text = tmpl.render(c)
- with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
- f.write(config_text)
+ conf = '/etc/logrotate.d/vyos-rsyslog'
+ render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True)
def verify(c):
@@ -253,8 +244,8 @@ def verify(c):
def apply(c):
if not c:
- return run('systemctl stop syslog')
- return run('systemctl restart syslog')
+ return run('systemctl stop syslog.service')
+ return run('systemctl restart syslog.service')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py
index 2f8dc9e89..25b949a79 100755
--- a/src/conf_mode/system-timezone.py
+++ b/src/conf_mode/system-timezone.py
@@ -20,7 +20,7 @@ import os
from copy import deepcopy
from vyos.config import Config
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
default_config_data = {
@@ -42,7 +42,7 @@ def generate(tz):
pass
def apply(tz):
- run('/usr/bin/timedatectl set-timezone {}'.format(tz['name']))
+ call('/usr/bin/timedatectl set-timezone {}'.format(tz['name']))
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py
index 943c42274..b222df0a9 100755
--- a/src/conf_mode/system-wifi-regdom.py
+++ b/src/conf_mode/system-wifi-regdom.py
@@ -18,11 +18,11 @@ import os
from copy import deepcopy
from sys import exit
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
+from vyos.template import render
+
config_80211_file='/etc/modprobe.d/cfg80211.conf'
config_crda_file='/etc/default/crda'
@@ -67,21 +67,8 @@ def generate(regdom):
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'wifi')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('cfg80211.conf.tmpl')
- config_text = tmpl.render(regdom)
- with open(config_80211_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('crda.tmpl')
- config_text = tmpl.render(regdom)
- with open(config_crda_file, 'w') as f:
- f.write(config_text)
-
+ render(config_80211_file, 'wifi/cfg80211.conf.tmpl', regdom)
+ render(config_crda_file, 'wifi/crda.tmpl', regdom)
return None
def apply(regdom):
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index df8155084..94c8bcf03 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -20,14 +20,13 @@ import pwd
from copy import deepcopy
from glob import glob
-from jinja2 import FileSystemLoader, Environment
from sys import exit
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos.validate import is_ipv4, is_addr_assigned
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
config_file = r'/etc/default/tftpd'
@@ -90,11 +89,6 @@ def generate(tftpd):
if tftpd is None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'tftp-server')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
idx = 0
for listen in tftpd['listen']:
config = deepcopy(tftpd)
@@ -103,11 +97,8 @@ def generate(tftpd):
else:
config['listen'] = ["[" + listen + "]" + tftpd['port'] + " -6"]
- tmpl = env.get_template('default.tmpl')
- config_text = tmpl.render(config)
file = config_file + str(idx)
- with open(file, 'w') as f:
- f.write(config_text)
+ render(file, 'tftp-server/default.tmpl', config)
idx = idx + 1
@@ -115,7 +106,7 @@ def generate(tftpd):
def apply(tftpd):
# stop all services first - then we will decide
- run('systemctl stop tftpd@{0..20}')
+ call('systemctl stop tftpd@{0..20}.service')
# bail out early - e.g. service deletion
if tftpd is None:
@@ -140,7 +131,7 @@ def apply(tftpd):
idx = 0
for listen in tftpd['listen']:
- run('systemctl restart tftpd@{0}.service'.format(idx))
+ call('systemctl restart tftpd@{0}.service'.format(idx))
idx = idx + 1
return None
diff --git a/src/conf_mode/vpn-pptp.py b/src/conf_mode/vpn-pptp.py
index 45b2c4b40..15b80f984 100755
--- a/src/conf_mode/vpn-pptp.py
+++ b/src/conf_mode/vpn-pptp.py
@@ -17,15 +17,15 @@
import os
import re
-from jinja2 import FileSystemLoader, Environment
from socket import socket, AF_INET, SOCK_STREAM
from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
from vyos.util import run
+from vyos.template import render
+
pidfile = r'/var/run/accel_pptp.pid'
pptp_cnf_dir = r'/etc/accel-ppp/pptp'
@@ -206,11 +206,6 @@ def generate(c):
if c == None:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'pptp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
-
# accel-cmd reload doesn't work so any change results in a restart of the daemon
try:
if os.cpu_count() == 1:
@@ -223,19 +218,13 @@ def generate(c):
else:
c['thread_cnt'] = int(os.cpu_count()/2)
- tmpl = env.get_template('pptp.config.tmpl')
- config_text = tmpl.render(c)
- with open(pptp_conf, 'w') as f:
- f.write(config_text)
+ render(pptp_conf, 'pptp/pptp.config.tmpl', c, trim_blocks=True)
if c['authentication']['local-users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- chap_secrets_txt = tmpl.render(c)
old_umask = os.umask(0o077)
- with open(chap_secrets, 'w') as f:
- f.write(chap_secrets_txt)
+ render(chap_secrets, 'pptp/chap-secrets.tmpl', c, trim_blocks=True)
os.umask(old_umask)
-
+ # return c ??
return c
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
new file mode 100755
index 000000000..a8b183bef
--- /dev/null
+++ b/src/conf_mode/vpn_l2tp.py
@@ -0,0 +1,386 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+
+from copy import deepcopy
+from stat import S_IRUSR, S_IWUSR, S_IRGRP
+from sys import exit
+from time import sleep
+
+from ipaddress import ip_network
+
+from vyos.config import Config
+from vyos.util import call
+from vyos.validate import is_ipv4
+from vyos import ConfigError
+from vyos.template import render
+
+
+l2tp_conf = '/run/accel-pppd/l2tp.conf'
+l2tp_chap_secrets = '/run/accel-pppd/l2tp.chap-secrets'
+
+default_config_data = {
+ 'auth_mode': 'local',
+ 'auth_ppp_mppe': 'prefer',
+ 'auth_proto': ['auth_mschap_v2'],
+ 'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template
+ 'client_ip_pool': None,
+ 'client_ip_subnets': [],
+ 'client_ipv6_pool': [],
+ 'client_ipv6_delegate_prefix': [],
+ 'dnsv4': [],
+ 'dnsv6': [],
+ 'gateway_address': '10.255.255.0',
+ 'local_users' : [],
+ 'mtu': '1436',
+ 'outside_addr': '',
+ 'ppp_mppe': 'prefer',
+ 'ppp_echo_failure' : '3',
+ 'ppp_echo_interval' : '30',
+ 'ppp_echo_timeout': '0',
+ 'radius_server': [],
+ 'radius_acct_tmo': '3',
+ 'radius_max_try': '3',
+ 'radius_timeout': '3',
+ 'radius_nas_id': '',
+ 'radius_nas_ip': '',
+ 'radius_source_address': '',
+ 'radius_shaper_attr': '',
+ 'radius_shaper_vendor': '',
+ 'radius_dynamic_author': '',
+ 'wins': [],
+ 'ip6_column': [],
+ 'thread_cnt': 1
+}
+
+def get_config():
+ conf = Config()
+ base_path = ['vpn', 'l2tp', 'remote-access']
+ if not conf.exists(base_path):
+ return None
+
+ conf.set_level(base_path)
+ l2tp = deepcopy(default_config_data)
+
+ cpu = os.cpu_count()
+ if cpu > 1:
+ l2tp['thread_cnt'] = int(cpu/2)
+
+ ### general options ###
+ if conf.exists(['name-server']):
+ for name_server in conf.return_values(['name-server']):
+ if is_ipv4(name_server):
+ l2tp['dnsv4'].append(name_server)
+ else:
+ l2tp['dnsv6'].append(name_server)
+
+ if conf.exists(['wins-server']):
+ l2tp['wins'] = conf.return_values(['wins-server'])
+
+ if conf.exists('outside-address'):
+ l2tp['outside_addr'] = conf.return_value('outside-address')
+
+ if conf.exists(['authentication', 'mode']):
+ l2tp['auth_mode'] = conf.return_value(['authentication', 'mode'])
+
+ if conf.exists(['authentication', 'protocols']):
+ auth_mods = {
+ 'pap': 'auth_pap',
+ 'chap': 'auth_chap_md5',
+ 'mschap': 'auth_mschap_v1',
+ 'mschap-v2': 'auth_mschap_v2'
+ }
+
+ for proto in conf.return_values(['authentication', 'protocols']):
+ l2tp['auth_proto'].append(auth_mods[proto])
+
+ if conf.exists(['authentication', 'mppe']):
+ l2tp['auth_ppp_mppe'] = conf.return_value(['authentication', 'mppe'])
+
+ #
+ # local auth
+ if conf.exists(['authentication', 'local-users']):
+ for username in conf.list_nodes(['authentication', 'local-users', 'username']):
+ user = {
+ 'name' : username,
+ 'password' : '',
+ 'state' : 'enabled',
+ 'ip' : '*',
+ 'upload' : None,
+ 'download' : None
+ }
+
+ conf.set_level(base_path + ['authentication', 'local-users', 'username', username])
+
+ if conf.exists(['password']):
+ user['password'] = conf.return_value(['password'])
+
+ if conf.exists(['disable']):
+ user['state'] = 'disable'
+
+ if conf.exists(['static-ip']):
+ user['ip'] = conf.return_value(['static-ip'])
+
+ if conf.exists(['rate-limit', 'download']):
+ user['download'] = conf.return_value(['rate-limit', 'download'])
+
+ if conf.exists(['rate-limit', 'upload']):
+ user['upload'] = conf.return_value(['rate-limit', 'upload'])
+
+ l2tp['local_users'].append(user)
+
+ #
+ # RADIUS auth and settings
+ conf.set_level(base_path + ['authentication', 'radius'])
+ if conf.exists(['server']):
+ for server in conf.list_nodes(['server']):
+ radius = {
+ 'server' : server,
+ 'key' : '',
+ 'fail_time' : 0,
+ 'port' : '1812'
+ }
+
+ conf.set_level(base_path + ['authentication', 'radius', 'server', server])
+
+ if conf.exists(['fail-time']):
+ radius['fail-time'] = conf.return_value(['fail-time'])
+
+ if conf.exists(['port']):
+ radius['port'] = conf.return_value(['port'])
+
+ if conf.exists(['key']):
+ radius['key'] = conf.return_value(['key'])
+
+ if not conf.exists(['disable']):
+ l2tp['radius_server'].append(radius)
+
+ #
+ # advanced radius-setting
+ conf.set_level(base_path + ['authentication', 'radius'])
+
+ if conf.exists(['acct-timeout']):
+ l2tp['radius_acct_tmo'] = conf.return_value(['acct-timeout'])
+
+ if conf.exists(['max-try']):
+ l2tp['radius_max_try'] = conf.return_value(['max-try'])
+
+ if conf.exists(['timeout']):
+ l2tp['radius_timeout'] = conf.return_value(['timeout'])
+
+ if conf.exists(['nas-identifier']):
+ l2tp['radius_nas_id'] = conf.return_value(['nas-identifier'])
+
+ if conf.exists(['nas-ip-address']):
+ l2tp['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
+
+ if conf.exists(['source-address']):
+ l2tp['radius_source_address'] = conf.return_value(['source-address'])
+
+ # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA)
+ if conf.exists(['dynamic-author']):
+ dae = {
+ 'port' : '',
+ 'server' : '',
+ 'key' : ''
+ }
+
+ if conf.exists(['dynamic-author', 'server']):
+ dae['server'] = conf.return_value(['dynamic-author', 'server'])
+
+ if conf.exists(['dynamic-author', 'port']):
+ dae['port'] = conf.return_value(['dynamic-author', 'port'])
+
+ if conf.exists(['dynamic-author', 'key']):
+ dae['key'] = conf.return_value(['dynamic-author', 'key'])
+
+ l2tp['radius_dynamic_author'] = dae
+
+ if conf.exists(['rate-limit', 'enable']):
+ l2tp['radius_shaper_attr'] = 'Filter-Id'
+ c_attr = ['rate-limit', 'enable', 'attribute']
+ if conf.exists(c_attr):
+ l2tp['radius_shaper_attr'] = conf.return_value(c_attr)
+
+ c_vendor = ['rate-limit', 'enable', 'vendor']
+ if conf.exists(c_vendor):
+ l2tp['radius_shaper_vendor'] = conf.return_value(c_vendor)
+
+ conf.set_level(base_path)
+ if conf.exists(['client-ip-pool']):
+ if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
+ start = conf.return_value(['client-ip-pool', 'start'])
+ stop = conf.return_value(['client-ip-pool', 'stop'])
+ l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+
+ if conf.exists(['client-ip-pool', 'subnet']):
+ l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet'])
+
+ if conf.exists(['client-ipv6-pool', 'prefix']):
+ l2tp['ip6_column'].append('ip6')
+ for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']):
+ tmp = {
+ 'prefix': prefix,
+ 'mask': '64'
+ }
+
+ if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']):
+ tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask'])
+
+ l2tp['client_ipv6_pool'].append(tmp)
+
+ if conf.exists(['client-ipv6-pool', 'delegate']):
+ l2tp['ip6_column'].append('ip6-db')
+ for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']):
+ tmp = {
+ 'prefix': prefix,
+ 'mask': ''
+ }
+
+ if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'mask']):
+ tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix'])
+
+ l2tp['client_ipv6_delegate_prefix'].append(tmp)
+
+ if conf.exists(['mtu']):
+ l2tp['mtu'] = conf.return_value(['mtu'])
+
+ # gateway address
+ if conf.exists(['gateway-address']):
+ l2tp['gateway_address'] = conf.return_value(['gateway-address'])
+ else:
+ # calculate gw-ip-address
+ if conf.exists(['client-ip-pool', 'start']):
+ # use start ip as gw-ip-address
+ l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
+
+ elif conf.exists(['client-ip-pool', 'subnet']):
+ # use first ip address from first defined pool
+ subnet = conf.return_values(['client-ip-pool', 'subnet'])[0]
+ subnet = ip_network(subnet)
+ l2tp['gateway_address'] = str(list(subnet.hosts())[0])
+
+ # LNS secret
+ if conf.exists(['lns', 'shared-secret']):
+ l2tp['lns_shared_secret'] = conf.return_value(['lns', 'shared-secret'])
+
+ if conf.exists(['ccp-disable']):
+ l2tp[['ccp_disable']] = True
+
+ # PPP options
+ if conf.exists(['idle']):
+ l2tp['ppp_echo_timeout'] = conf.return_value(['idle'])
+
+ if conf.exists(['ppp-options', 'lcp-echo-failure']):
+ l2tp['ppp_echo_failure'] = conf.return_value(['ppp-options', 'lcp-echo-failure'])
+
+ if conf.exists(['ppp-options', 'lcp-echo-interval']):
+ l2tp['ppp_echo_interval'] = conf.return_value(['ppp-options', 'lcp-echo-interval'])
+
+ return l2tp
+
+
+def verify(l2tp):
+ if not l2tp:
+ return None
+
+ if l2tp['auth_mode'] == 'local':
+ if not l2tp['local_users']:
+ raise ConfigError('L2TP local auth mode requires local users to be configured!')
+
+ for user in l2tp['local_users']:
+ if not user['password']:
+ raise ConfigError(f"Password required for user {user['name']}")
+
+ elif l2tp['auth_mode'] == 'radius':
+ if len(l2tp['radius_server']) == 0:
+ raise ConfigError("RADIUS authentication requires at least one server")
+
+ for radius in l2tp['radius_server']:
+ if not radius['key']:
+ raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}")
+
+ # check for the existence of a client ip pool
+ if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']):
+ raise ConfigError(
+ "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
+
+ # check ipv6
+ if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']:
+ raise ConfigError('IPv6 prefix delegation requires client-ipv6-pool prefix')
+
+ for prefix in l2tp['client_ipv6_delegate_prefix']:
+ if not prefix['mask']:
+ raise ConfigError('Delegation-prefix required for individual delegated networks')
+
+ if len(l2tp['wins']) > 2:
+ raise ConfigError('Not more then two IPv4 WINS name-servers can be configured')
+
+ if len(l2tp['dnsv4']) > 2:
+ raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
+
+ if len(l2tp['dnsv6']) > 3:
+ raise ConfigError('Not more then three IPv6 DNS name-servers can be configured')
+
+ return None
+
+
+def generate(l2tp):
+ if not l2tp:
+ return None
+
+ dirname = os.path.dirname(l2tp_conf)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+
+ render(l2tp_conf, 'l2tp/l2tp.config.tmpl', c, trim_blocks=True)
+
+ if l2tp['auth_mode'] == 'local':
+ render(l2tp_chap_secrets, 'l2tp/chap-secrets.tmpl', l2tp)
+ os.chmod(l2tp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
+
+ else:
+ if os.path.exists(l2tp_chap_secrets):
+ os.unlink(l2tp_chap_secrets)
+
+ return None
+
+
+def apply(l2tp):
+ if not l2tp:
+ call('systemctl stop accel-ppp@l2tp.service')
+
+ if os.path.exists(l2tp_conf):
+ os.unlink(l2tp_conf)
+
+ if os.path.exists(l2tp_chap_secrets):
+ os.unlink(l2tp_chap_secrets)
+
+ return None
+
+ call('systemctl restart accel-ppp@l2tp.service')
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index ca0844c50..438731972 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -18,49 +18,24 @@ import os
from time import sleep
from sys import exit
-from socket import socket, AF_INET, SOCK_STREAM
from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
-from jinja2 import FileSystemLoader, Environment
from vyos.config import Config
from vyos import ConfigError
-from vyos.defaults import directories as vyos_data_dir
-from vyos.util import process_running
-from vyos.util import process_running, cmd, run
-
-pidfile = r'/var/run/accel_sstp.pid'
-sstp_cnf_dir = r'/etc/accel-ppp/sstp'
-chap_secrets = sstp_cnf_dir + '/chap-secrets'
-sstp_conf = sstp_cnf_dir + '/sstp.config'
-
-# config path creation
-if not os.path.exists(sstp_cnf_dir):
- os.makedirs(sstp_cnf_dir)
-
-def chk_con():
- cnt = 0
- s = socket(AF_INET, SOCK_STREAM)
- while True:
- try:
- s.connect(("127.0.0.1", 2005))
- s.close()
- break
- except ConnectionRefusedError:
- sleep(0.5)
- cnt += 1
- if cnt == 100:
- raise("failed to start sstp server")
- break
-
-
-def _accel_cmd(command):
- return run(f'/usr/bin/accel-cmd -p 2005 {command}')
+from vyos.util import call, run
+from vyos.template import render
+
+
+sstp_conf = '/run/accel-pppd/sstp.conf'
+sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets'
default_config_data = {
'local_users' : [],
'auth_mode' : 'local',
- 'auth_proto' : [],
+ 'auth_proto' : ['auth_mschap_v2'],
+ 'chap_secrets_file': sstp_chap_secrets, # used in Jinja2 template
+ 'client_gateway': '',
'radius_server' : [],
'radius_acct_tmo' : '3',
'radius_max_try' : '3',
@@ -77,11 +52,11 @@ default_config_data = {
'client_ip_pool' : [],
'dnsv4' : [],
'mtu' : '',
- 'ppp_mppe' : '',
+ 'ppp_mppe' : 'prefer',
'ppp_echo_failure' : '',
'ppp_echo_interval' : '',
'ppp_echo_timeout' : '',
- 'thread_cnt' : ''
+ 'thread_cnt' : 1
}
def get_config():
@@ -93,10 +68,9 @@ def get_config():
conf.set_level(base_path)
- cpu = int(os.cpu_count()/2)
- if cpu < 1:
- cpu = 1
- sstp['thread_cnt'] = cpu
+ cpu = os.cpu_count()
+ if cpu > 1:
+ sstp['thread_cnt'] = int(cpu/2)
if conf.exists(['authentication', 'mode']):
sstp['auth_mode'] = conf.return_value(['authentication', 'mode'])
@@ -214,6 +188,8 @@ def get_config():
# authentication protocols
conf.set_level(base_path + ['authentication'])
if conf.exists(['protocols']):
+ # clear default list content, now populate with actual CLI values
+ sstp['auth_proto'] = []
auth_mods = {
'pap': 'auth_pap',
'chap': 'auth_chap_md5',
@@ -224,9 +200,6 @@ def get_config():
for proto in conf.return_values(['protocols']):
sstp['auth_proto'].append(auth_mods[proto])
- else:
- sstp['auth_proto'] = ['auth_mschap_v2']
-
#
# read in SSL certs
conf.set_level(base_path + ['ssl'])
@@ -262,7 +235,7 @@ def get_config():
# read in PPP stuff
conf.set_level(base_path + ['ppp-settings'])
if conf.exists('mppe'):
- sstp['ppp_mppe'] = conf.return_value('ppp-settings mppe')
+ sstp['ppp_mppe'] = conf.return_value(['ppp-settings', 'mppe'])
if conf.exists(['lcp-echo-failure']):
sstp['ppp_echo_failure'] = conf.return_value(['lcp-echo-failure'])
@@ -283,7 +256,7 @@ def verify(sstp):
# vertify auth settings
if sstp['auth_mode'] == 'local':
if not sstp['local_users']:
- raise ConfigError('sstp-server authentication local-users required')
+ raise ConfigError('SSTP local auth mode requires local users to be configured!')
for user in sstp['local_users']:
if not user['password']:
@@ -303,7 +276,7 @@ def verify(sstp):
raise ConfigError("Client gateway IP address required")
if len(sstp['dnsv4']) > 2:
- raise ConfigError("Only 2 DNS name-servers can be configured")
+ raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
if not sstp['ssl_ca'] or not sstp['ssl_cert'] or not sstp['ssl_key']:
raise ConfigError('One or more SSL certificates missing')
@@ -326,69 +299,38 @@ def verify(sstp):
raise ConfigError(f"Missing RADIUS secret for server {{ radius['key'] }}")
def generate(sstp):
- if sstp is None:
+ if not sstp:
return None
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'sstp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader, trim_blocks=True)
+ dirname = os.path.dirname(sstp_conf)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
# accel-cmd reload doesn't work so any change results in a restart of the daemon
- tmpl = env.get_template('sstp.config.tmpl')
- config_text = tmpl.render(sstp)
- with open(sstp_conf, 'w') as f:
- f.write(config_text)
+ render(sstp_conf, 'sstp/sstp.config.tmpl', sstp, trim_blocks=True)
if sstp['local_users']:
- tmpl = env.get_template('chap-secrets.tmpl')
- config_text = tmpl.render(sstp)
- with open(chap_secrets, 'w') as f:
- f.write(config_text)
-
- os.chmod(chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
+ render(sstp_chap_secrets, 'sstp/chap-secrets.tmpl', sstp, trim_blocks=True)
+ os.chmod(sstp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP)
else:
- if os.path.exists(chap_secrets):
- os.unlink(chap_secrets)
+ if os.path.exists(sstp_chap_secrets):
+ os.unlink(sstp_chap_secrets)
return sstp
def apply(sstp):
- if sstp is None:
- if process_running(pidfile):
- command = 'start-stop-daemon'
- command += ' --stop '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- cmd(command)
+ if not sstp:
+ call('systemctl stop accel-ppp@sstp.service')
- if os.path.exists(pidfile):
- os.remove(pidfile)
+ if os.path.exists(sstp_conf):
+ os.unlink(sstp_conf)
- return None
+ if os.path.exists(sstp_chap_secrets):
+ os.unlink(sstp_chap_secrets)
- if not process_running(pidfile):
- if os.path.exists(pidfile):
- os.remove(pidfile)
-
- command = 'start-stop-daemon'
- command += ' --start '
- command += ' --quiet'
- command += ' --oknodo'
- command += ' --pidfile ' + pidfile
- command += ' --exec /usr/sbin/accel-pppd'
- # now pass arguments to accel-pppd binary
- command += ' --'
- command += ' -c ' + sstp_conf
- command += ' -p ' + pidfile
- command += ' -d'
- cmd(command)
-
- chk_con()
+ return None
- else:
- _accel_cmd('restart')
+ call('systemctl restart accel-ppp@sstp.service')
if __name__ == '__main__':
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 07466f3aa..eb73293a9 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -18,15 +18,15 @@ import os
from sys import exit
from copy import deepcopy
-from jinja2 import FileSystemLoader, Environment
from json import loads
from vyos.config import Config
from vyos.configdict import list_diff
-from vyos.defaults import directories as vyos_data_dir
from vyos.ifconfig import Interface
from vyos.util import read_file, cmd
from vyos import ConfigError
+from vyos.template import render
+
config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
@@ -178,16 +178,7 @@ def verify(vrf_config):
return None
def generate(vrf_config):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrf')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
- tmpl = env.get_template('vrf.conf.tmpl')
- config_text = tmpl.render(vrf_config)
- with open(config_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'vrf/vrf.conf.tmpl', vrf_config)
return None
def apply(vrf_config):
@@ -195,6 +186,7 @@ def apply(vrf_config):
#
# - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt
# - https://github.com/Mellanox/mlxsw/wiki/Virtual-Routing-and-Forwarding-(VRF)
+ # - https://github.com/Mellanox/mlxsw/wiki/L3-Tunneling
# - https://netdevconf.info/1.1/proceedings/slides/ahern-vrf-tutorial.pdf
# - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index d3e3710d1..b9b0405e2 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -18,16 +18,16 @@ import os
from sys import exit
from ipaddress import ip_address, ip_interface, IPv4Interface, IPv6Interface, IPv4Address, IPv6Address
-from jinja2 import FileSystemLoader, Environment
from json import dumps
from pathlib import Path
import vyos.config
import vyos.keepalived
-from vyos.defaults import directories as vyos_data_dir
from vyos import ConfigError
-from vyos.util import run
+from vyos.util import call
+from vyos.template import render
+
daemon_file = "/etc/default/keepalived"
config_file = "/etc/keepalived/keepalived.conf"
@@ -201,11 +201,6 @@ def verify(data):
def generate(data):
- # Prepare Jinja2 template loader from files
- tmpl_path = os.path.join(vyos_data_dir['data'], 'templates', 'vrrp')
- fs_loader = FileSystemLoader(tmpl_path)
- env = Environment(loader=fs_loader)
-
vrrp_groups, sync_groups = data
# Remove disabled groups from the sync group member lists
@@ -217,16 +212,9 @@ def generate(data):
# Filter out disabled groups
vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups))
- tmpl = env.get_template('keepalived.conf.tmpl')
- config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
- with open(config_file, 'w') as f:
- f.write(config_text)
-
- tmpl = env.get_template('daemon.tmpl')
- config_text = tmpl.render()
- with open(daemon_file, 'w') as f:
- f.write(config_text)
-
+ render(config_file, 'vrrp/keepalived.conf.tmpl',
+ {"groups": vrrp_groups, "sync_groups": sync_groups})
+ render(daemon_file, 'vrrp/daemon.tmpl', {})
return None
@@ -242,17 +230,17 @@ def apply(data):
if not vyos.keepalived.vrrp_running():
print("Starting the VRRP process")
- ret = run("sudo systemctl restart keepalived.service")
+ ret = call("sudo systemctl restart keepalived.service")
else:
print("Reloading the VRRP process")
- ret = run("sudo systemctl reload keepalived.service")
+ ret = call("sudo systemctl reload keepalived.service")
if ret != 0:
raise ConfigError("keepalived failed to start")
else:
# VRRP is removed in the commit
print("Stopping the VRRP process")
- run("sudo systemctl stop keepalived.service")
+ call("sudo systemctl stop keepalived.service")
os.unlink(config_file)
return None
diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
index dcd06644f..eeb8b0782 100644
--- a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
+++ b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook
@@ -22,8 +22,13 @@
# To enable this script set the following variable to "yes"
RUN="yes"
+proto=""
+if [[ $reason =~ (REBOOT6|INIT6|EXPIRE6|RELEASE6|STOP6|INFORM6|BOUND6|REBIND6|DELEGATED6) ]]; then
+ proto="v6"
+fi
+
if [ "$RUN" = "yes" ]; then
- LOG=/var/lib/dhcp/dhclient_"$interface"_lease
+ LOG=/var/lib/dhcp/dhclient_"$interface"."$proto"lease
echo `date` > $LOG
for i in reason interface new_expiry new_dhcp_lease_time medium \
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/helpers/validate-value.py b/src/helpers/validate-value.py
index fab6ca81e..a58ba61d1 100755
--- a/src/helpers/validate-value.py
+++ b/src/helpers/validate-value.py
@@ -5,7 +5,7 @@ import os
import sys
import argparse
-from vyos.util import run
+from vyos.util import call
parser = argparse.ArgumentParser()
parser.add_argument('--regex', action='append')
@@ -33,7 +33,7 @@ try:
cmd = "{0} {1}".format(cmd, args.value)
if debug:
print(cmd)
- res = run(cmd)
+ res = call(cmd)
if res == 0:
sys.exit(0)
except Exception as exn:
diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py
index 6546c03e3..14df2734b 100755
--- a/src/helpers/vyos-merge-config.py
+++ b/src/helpers/vyos-merge-config.py
@@ -23,7 +23,7 @@ import vyos.remote
from vyos.config import Config
from vyos.configtree import ConfigTree
from vyos.migrator import Migrator, VirtualMigrator
-from vyos.util import cmd
+from vyos.util import cmd, DEVNULL
if (len(sys.argv) < 2):
@@ -99,9 +99,9 @@ if (len(sys.argv) > 2):
if path:
add_cmds = [ cmd for cmd in add_cmds if path in cmd ]
-for cmd in add_cmds:
+for add in add_cmds:
try:
- cmd(f'/opt/vyatta/sbin/my_{cmd}', message='Called process error')
+ cmd(f'/opt/vyatta/sbin/my_{add}', shell=True, stderr=DEVNULL)
except OSError as err:
print(err)
diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8
index 78bd2781b..8830ffdc7 100755
--- a/src/migration-scripts/interfaces/7-to-8
+++ b/src/migration-scripts/interfaces/7-to-8
@@ -14,7 +14,8 @@
# 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 network provider name from CLI and rather use provider APN from CLI
+# Split WireGuard endpoint into address / port nodes to make use of common
+# validators
from sys import exit, argv
from vyos.configtree import ConfigTree
diff --git a/src/migration-scripts/interfaces/8-to-9 b/src/migration-scripts/interfaces/8-to-9
new file mode 100755
index 000000000..e0b9dd375
--- /dev/null
+++ b/src/migration-scripts/interfaces/8-to-9
@@ -0,0 +1,52 @@
+#!/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/>.
+
+# Rename link nodes to source-interface for the following interface types:
+# - vxlan
+# - pseudo ethernet
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+ 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)
+
+ for if_type in ['vxlan', 'pseudo-ethernet']:
+ base = ['interfaces', if_type]
+ if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+ # list all individual interface isntance
+ for i in config.list_nodes(base):
+ iface = base + [i]
+ if config.exists(iface + ['link']):
+ config.rename(iface + ['link'], 'source-interface')
+
+ 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/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/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index 192fd80ec..b191f630d 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -21,7 +21,7 @@ from sys import exit
from psutil import process_iter
from time import strftime, localtime, time
-from vyos.util import run
+from vyos.util import call
PPP_LOGFILE = '/var/log/vyatta/ppp_{}.log'
@@ -59,7 +59,7 @@ def connect(interface):
tm = strftime("%a %d %b %Y %I:%M:%S %p %Z", localtime(time()))
with open(PPP_LOGFILE.format(interface), 'a') as f:
f.write('{}: user {} started PPP daemon for {} by connect command\n'.format(tm, user, interface))
- run('umask 0; setsid sh -c "nohup /usr/sbin/pppd call {0} > /tmp/{0}.log 2>&1 &"'.format(interface))
+ call('umask 0; setsid sh -c "nohup /usr/sbin/pppd call {0} > /tmp/{0}.log 2>&1 &"'.format(interface))
def disconnect(interface):
@@ -77,7 +77,7 @@ def disconnect(interface):
tm = strftime("%a %d %b %Y %I:%M:%S %p %Z", localtime(time()))
with open(PPP_LOGFILE.format(interface), 'a') as f:
f.write('{}: user {} stopped PPP daemon for {} by disconnect command\n'.format(tm, user, interface))
- run('/usr/bin/poff "{}"'.format(interface))
+ call('/usr/bin/poff "{}"'.format(interface))
def main():
parser = argparse.ArgumentParser()
diff --git a/src/op_mode/dns_forwarding_reset.py b/src/op_mode/dns_forwarding_reset.py
index 93c2444b9..8e2ee546c 100755
--- a/src/op_mode/dns_forwarding_reset.py
+++ b/src/op_mode/dns_forwarding_reset.py
@@ -21,12 +21,11 @@
import os
-import sys
import argparse
-import vyos.config
-from vyos.util import run
-
+from sys import exit
+from vyos.config import Config
+from vyos.util import call
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--all", action="store_true", help="Reset all cache")
@@ -36,16 +35,18 @@ if __name__ == '__main__':
args = parser.parse_args()
# Do nothing if service is not configured
- c = vyos.config.Config()
- if not c.exists_effective('service dns forwarding'):
+ c = Config()
+ if not c.exists_effective(['service', 'dns', 'forwarding']):
print("DNS forwarding is not configured")
- sys.exit(0)
+ exit(0)
if args.all:
- run("rec_control wipe-cache \'.$\'")
- sys.exit(1)
+ call("rec_control wipe-cache \'.$\'")
+ exit(0)
+
elif args.domain:
- run("rec_control wipe-cache \'{0}$\'".format(args.domain))
+ call("rec_control wipe-cache \'{0}$\'".format(args.domain))
+
else:
parser.print_help()
- sys.exit(1)
+ 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 d991848ad..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
@@ -21,10 +21,9 @@ import sys
import time
from vyos.config import Config
-from vyos.util import run
+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():
- run('systemctl stop ddclient')
+ call('systemctl stop ddclient.service')
os.remove(cache_file)
- run('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/format_disk.py b/src/op_mode/format_disk.py
index 9d3797f17..df4486bce 100755
--- a/src/op_mode/format_disk.py
+++ b/src/op_mode/format_disk.py
@@ -22,7 +22,9 @@ from datetime import datetime
from time import sleep
from vyos.util import is_admin, ask_yes_no
-from vyos.util import run, cmd, DEVNULL
+from vyos.util import call
+from vyos.util import cmd
+from vyos.util import DEVNULL
def list_disks():
disks = set()
@@ -36,7 +38,7 @@ def list_disks():
def is_busy(disk: str):
"""Check if given disk device is busy by re-reading it's partition table"""
- return run(f'sudo blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0
+ return call(f'sudo blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0
def backup_partitions(disk: str):
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/lldp_op.py b/src/op_mode/lldp_op.py
index c8a5543b6..5d48e3210 100755
--- a/src/op_mode/lldp_op.py
+++ b/src/op_mode/lldp_op.py
@@ -23,6 +23,7 @@ from sys import exit
from tabulate import tabulate
from vyos.util import popen
+from vyos.config import Config
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces")
@@ -141,6 +142,11 @@ if __name__ == '__main__':
args = parser.parse_args()
tmp = { 'neighbors' : [] }
+ c = Config()
+ if not c.exists_effective(['service', 'lldp']):
+ print('Service LLDP is not configured')
+ exit(0)
+
if args.all:
neighbors = minidom.parseString(_get_neighbors())
for neighbor in neighbors.getElementsByTagName('interface'):
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 772bb8198..4ab91384b 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -21,7 +21,10 @@ import re
from datetime import datetime, timedelta, time as type_time, date as type_date
from vyos.util import ask_yes_no
-from vyos.util import cmd, run
+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"
@@ -95,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:
@@ -113,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]))
@@ -132,7 +135,7 @@ def chk_vyatta_based_reboots():
if os.path.exists(f):
jid = open(f).read().strip()
if jid != 0:
- run(f'sudo atrm {jid}')
+ call(f'sudo atrm {jid}')
os.remove(f)
def main():
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/reset_vpn.py b/src/op_mode/reset_vpn.py
index b47212f88..3a0ad941c 100755
--- a/src/op_mode/reset_vpn.py
+++ b/src/op_mode/reset_vpn.py
@@ -14,63 +14,49 @@
# 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 sys
import argparse
-#import re
-from vyos.util import run, DEVNULL
+from vyos.util import run
-pptp_base = '/usr/bin/accel-cmd -p 2003 terminate {} {}'
-l2tp_base = '/usr/bin/accel-cmd -p 2004 terminate {} {}'
+cmd_dict = {
+ 'cmd_base' : '/usr/bin/accel-cmd -p {} terminate {} {}',
+ 'vpn_types' : {
+ 'pptp' : 2003,
+ 'l2tp' : 2004,
+ 'sstp' : 2005
+ }
+}
def terminate_sessions(username='', interface='', protocol=''):
- if username:
- if username == "all_users":
- if protocol == "pptp":
- pptp_cmd = pptp_base.format('all','')
- run(pptp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
- elif protocol == "l2tp":
- l2tp_cmd = l2tp_base.format('all', '')
- run(l2tp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
- else:
- pptp_cmd = pptp_base.format('all', '')
- run(pptp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- l2tp_cmd = l2tp_base.format('all', '')
- run(l2tp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
- if protocol == "pptp":
- pptp_cmd = pptp_base.format('username', username)
- run(pptp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
- elif protocol == "l2tp":
- l2tp_cmd = l2tp_base.format('username', username)
- run(l2tp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
+ # Reset vpn connections by username
+ if protocol in cmd_dict['vpn_types']:
+ if username == "all_users":
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'all', ''))
else:
- pptp_cmd = pptp_base.format('username', username)
- run(pptp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- l2tp_cmd.append("terminate username {0}".format(username))
- run(l2tp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- return
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][protocol], 'username', username))
+
+ # Reset vpn connections by ifname
+ elif interface:
+ for proto in cmd_dict['vpn_types']:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'if', interface))
- # rewrite `terminate by interface` if pptp will have pptp%d interface naming
- if interface:
- pptp_cmd = pptp_base.format('if', interface)
- run(pptp_cmd, stdout=DEVNULL, stderr=DEVNULL)
- l2tp_cmd = l2tp_base.format('if', interface)
- run(l2tp_cmd, stdout=DEVNULL, stderr=DEVNULL)
-
+ elif username:
+ # Reset all vpn connections
+ if username == "all_users":
+ for proto in cmd_dict['vpn_types']:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'all', ''))
+ else:
+ for proto in cmd_dict['vpn_types']:
+ run(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][proto], 'username', username))
def main():
#parese args
parser = argparse.ArgumentParser()
parser.add_argument('--username', help='Terminate by username (all_users used for disconnect all users)', required=False)
parser.add_argument('--interface', help='Terminate by interface', required=False)
- parser.add_argument('--protocol', help='Set protocol (pptp|l2tp)', required=False)
+ parser.add_argument('--protocol', help='Set protocol (pptp|l2tp|sstp)', required=False)
args = parser.parse_args()
if args.username or args.interface:
diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py
index 057b4dcd8..af4fb2d15 100755
--- a/src/op_mode/restart_dhcp_relay.py
+++ b/src/op_mode/restart_dhcp_relay.py
@@ -23,7 +23,7 @@ import argparse
import os
import vyos.config
-from vyos.util import run
+from vyos.util import call
parser = argparse.ArgumentParser()
@@ -39,7 +39,7 @@ if __name__ == '__main__':
if not c.exists_effective('service dhcp-relay'):
print("DHCP relay service not configured")
else:
- run('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:
- run('sudo systemctl restart isc-dhcpv6-relay.service')
+ call('systemctl restart isc-dhcp-server6.service')
sys.exit(0)
else:
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 6304e72db..d1b66b33f 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -22,7 +22,7 @@ from logging.handlers import SysLogHandler
from pathlib import Path
import psutil
-from vyos.util import run
+from vyos.util import call
# some default values
watchfrr = '/usr/lib/frr/watchfrr.sh'
@@ -87,7 +87,7 @@ def _write_config():
Path(frrconfig_tmp).mkdir(parents=False, exist_ok=True)
# save frr.conf to it
command = "{} -n -w --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp)
- return_code = run(command)
+ return_code = call(command)
if not return_code == 0:
logger.error("Failed to save active config: \"{}\" returned exit code: {}".format(command, return_code))
return False
@@ -109,7 +109,7 @@ def _cleanup():
# check if daemon is running
def _daemon_check(daemon):
command = "{} print_status {}".format(watchfrr, daemon)
- return_code = run(command)
+ return_code = call(command)
if not return_code == 0:
logger.error("Daemon \"{}\" is not running".format(daemon))
return False
@@ -120,7 +120,7 @@ def _daemon_check(daemon):
# restart daemon
def _daemon_restart(daemon):
command = "{} restart {}".format(watchfrr, daemon)
- return_code = run(command)
+ return_code = call(command)
if not return_code == 0:
logger.error("Failed to restart daemon \"{}\"".format(daemon))
return False
@@ -136,7 +136,7 @@ def _reload_config(daemon):
else:
command = "{} -n -b --config_dir {} 2> /dev/null".format(vtysh, frrconfig_tmp)
- return_code = run(command)
+ return_code = call(command)
if not return_code == 0:
logger.error("Failed to reinstall configuration")
return False
diff --git a/src/op_mode/show_acceleration.py b/src/op_mode/show_acceleration.py
index 05d3d8906..6d44b0f66 100755
--- a/src/op_mode/show_acceleration.py
+++ b/src/op_mode/show_acceleration.py
@@ -21,7 +21,8 @@ import re
import argparse
from vyos.config import Config
-from vyos.util import popen, run
+from vyos.util import popen
+from vyos.util import call
def detect_qat_dev():
@@ -43,7 +44,7 @@ def show_qat_status():
sys.exit(1)
# Show QAT service
- run('sudo /etc/init.d/vyos-qat-utilities status')
+ call('sudo /etc/init.d/vyos-qat-utilities status')
# Return QAT devices
def get_qat_devices():
@@ -94,20 +95,20 @@ args = parser.parse_args()
if args.hw:
detect_qat_dev()
# Show availible Intel QAT devices
- run('sudo lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
+ call('sudo lspci -nn | egrep -e \'8086:37c8|8086:19e2|8086:0435|8086:6f54\'')
elif args.flow and args.dev:
check_qat_if_conf()
- run('sudo cat '+get_qat_proc_path(args.dev)+"fw_counters")
+ call('sudo cat '+get_qat_proc_path(args.dev)+"fw_counters")
elif args.interrupts:
check_qat_if_conf()
# Delete _dev from args.dev
- run('sudo cat /proc/interrupts | grep qat')
+ call('sudo cat /proc/interrupts | grep qat')
elif args.status:
check_qat_if_conf()
show_qat_status()
elif args.conf and args.dev:
check_qat_if_conf()
- run('sudo cat '+get_qat_proc_path(args.dev)+"dev_cfg")
+ call('sudo cat '+get_qat_proc_path(args.dev)+"dev_cfg")
elif args.dev_list:
get_qat_devices()
else:
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index 4e3e08263..c49e604b7 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -27,7 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import run
+from vyos.util import call
lease_file = "/config/dhcpd.leases"
@@ -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 run('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 4ef4849ff..d686defc0 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -27,7 +27,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
from vyos.config import Config
-from vyos.util import run
+from vyos.util import call
lease_file = "/config/dhcpdv6.leases"
pool_key = "shared-networkname"
@@ -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 run('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/snmp.py b/src/op_mode/snmp.py
index b09eab97f..5fae67881 100755
--- a/src/op_mode/snmp.py
+++ b/src/op_mode/snmp.py
@@ -24,7 +24,7 @@ import sys
import argparse
from vyos.config import Config
-from vyos.util import run
+from vyos.util import call
config_file_daemon = r'/etc/snmp/snmpd.conf'
@@ -54,7 +54,7 @@ def show_all():
def show_community(c, h):
print('Status of SNMP community {0} on {1}'.format(c, h), flush=True)
- run('/usr/bin/snmpstatus -t1 -v1 -c {0} {1}'.format(c, h))
+ call('/usr/bin/snmpstatus -t1 -v1 -c {0} {1}'.format(c, h))
if __name__ == '__main__':
args = parser.parse_args()
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index 34eca44b1..8599c958f 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -30,7 +30,10 @@ import pystache
import vyos.version
import vyos.limericks
-from vyos.util import cmd, run
+from vyos.util import cmd
+from vyos.util import call
+from vyos.util import run
+from vyos.util import DEVNULL
parser = argparse.ArgumentParser()
@@ -80,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
@@ -119,7 +122,7 @@ if __name__ == '__main__':
if args.all:
print("Package versions:")
- run("dpkg -l")
+ call("dpkg -l")
if args.funny:
print(vyos.limericks.get_random())
diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py
index d940d79eb..1b90f4fa7 100755
--- a/src/op_mode/wireguard.py
+++ b/src/op_mode/wireguard.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
@@ -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 argparse
import os
@@ -27,7 +25,7 @@ from vyos.ifconfig import WireGuardIf
from vyos import ConfigError
from vyos.config import Config
-from vyos.util import run
+from vyos.util import cmd, run
dir = r'/config/auth/wireguard'
psk = dir + '/preshared.key'
@@ -88,10 +86,11 @@ def genpsk():
it's stored only in the cli config
"""
- run('wg genpsk')
+ psk = cmd('wg genpsk')
+ print(psk)
def list_key_dirs():
- """ lists all dirs under /config/auth/wireguard """
+ """ lists all dirs under /config/auth/wireguard """
if os.path.exists(dir):
nks = next(os.walk(dir))[1]
for nk in nks:
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]