summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--data/configd-include.json4
-rw-r--r--data/templates/dhcp-client/daemon-options.j24
-rw-r--r--data/templates/dhcp-client/daemon-options.tmpl4
-rw-r--r--data/templates/dhcp-client/ipv4.j2 (renamed from data/templates/dhcp-client/ipv4.tmpl)10
-rw-r--r--data/templates/dhcp-client/ipv6.j2 (renamed from data/templates/dhcp-client/ipv6.tmpl)44
-rw-r--r--data/templates/dhcp-relay/dhcrelay.conf.j2 (renamed from data/templates/dhcp-relay/dhcrelay.conf.tmpl)0
-rw-r--r--data/templates/dhcp-relay/dhcrelay6.conf.j2 (renamed from data/templates/dhcp-relay/dhcrelay6.conf.tmpl)16
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.j2 (renamed from data/templates/dhcp-server/dhcpd.conf.tmpl)230
-rw-r--r--data/templates/dhcp-server/dhcpdv6.conf.j2132
-rw-r--r--data/templates/dhcp-server/dhcpdv6.conf.tmpl124
-rw-r--r--data/templates/dns-forwarding/recursor.conf.j2 (renamed from data/templates/dns-forwarding/recursor.conf.tmpl)1
-rw-r--r--data/templates/dns-forwarding/recursor.conf.lua.j2 (renamed from data/templates/dns-forwarding/recursor.conf.lua.tmpl)0
-rw-r--r--data/templates/dns-forwarding/recursor.forward-zones.conf.j2 (renamed from data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl)15
-rw-r--r--data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.j2 (renamed from data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl)22
-rw-r--r--data/templates/dns-forwarding/recursor.zone.conf.j2 (renamed from data/templates/dns-forwarding/recursor.zone.conf.tmpl)3
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.j251
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.tmpl51
-rw-r--r--data/templates/firewall/nftables-nat.tmpl15
-rw-r--r--data/templates/frr/staticd.frr.j28
-rw-r--r--data/templates/ipsec/swanctl/peer.tmpl2
-rw-r--r--data/templates/monitoring/telegraf.tmpl6
-rw-r--r--data/templates/openvpn/auth.pw.j2 (renamed from data/templates/openvpn/auth.pw.tmpl)0
-rw-r--r--data/templates/openvpn/client.conf.j2 (renamed from data/templates/openvpn/client.conf.tmpl)24
-rw-r--r--data/templates/openvpn/server.conf.j2 (renamed from data/templates/openvpn/server.conf.tmpl)186
-rw-r--r--data/templates/openvpn/service-override.conf.j221
-rw-r--r--data/templates/openvpn/service-override.conf.tmpl20
-rw-r--r--data/templates/salt-minion/minion.j2 (renamed from data/templates/salt-minion/minion.tmpl)18
-rw-r--r--data/templates/vyos-hostsd/hosts.j2 (renamed from data/templates/vyos-hostsd/hosts.tmpl)13
-rw-r--r--data/templates/vyos-hostsd/resolv.conf.j2 (renamed from data/templates/vyos-hostsd/resolv.conf.tmpl)14
-rw-r--r--interface-definitions/dhcp-server.xml.in38
-rw-r--r--interface-definitions/dhcpv6-server.xml.in27
-rw-r--r--interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i2
-rw-r--r--interface-definitions/include/interface/default-route-distance.xml.i15
-rw-r--r--interface-definitions/include/interface/dhcp-options.xml.i21
-rw-r--r--interface-definitions/include/interface/no-default-route.xml.i8
-rw-r--r--interface-definitions/include/interface/parameters-df.xml.i26
-rw-r--r--interface-definitions/include/interface/parameters-dont-fragment.xml.i8
-rw-r--r--interface-definitions/include/version/interfaces-version.xml.i2
-rw-r--r--interface-definitions/interfaces-geneve.xml.in2
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in27
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in2
-rw-r--r--interface-definitions/salt-minion.xml.in15
-rw-r--r--op-mode-definitions/monitor-log.xml.in109
-rw-r--r--op-mode-definitions/show-log.xml.in21
-rw-r--r--python/vyos/base.py8
-rw-r--r--python/vyos/config.py73
-rw-r--r--python/vyos/configdict.py118
-rw-r--r--python/vyos/configverify.py6
-rw-r--r--python/vyos/ifconfig/geneve.py2
-rwxr-xr-xpython/vyos/ifconfig/interface.py20
-rw-r--r--python/vyos/ifconfig/pppoe.py80
-rw-r--r--python/vyos/ifconfig/vxlan.py2
-rw-r--r--smoketest/configs/basic-vyos24
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py20
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py5
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_dummy.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py25
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_geneve.py6
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_l2tpv3.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_loopback.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py114
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py14
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pseudo_ethernet.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_tunnel.py2
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py4
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py9
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py2
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py111
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py2
-rwxr-xr-xsmoketest/scripts/cli/test_pki.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py28
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py2
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_mpls.py2
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospfv3.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static_arp.py88
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py4
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-server.py20
-rwxr-xr-xsmoketest/scripts/cli/test_service_ids.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_lldp.py4
-rwxr-xr-xsmoketest/scripts/cli/test_service_salt.py105
-rwxr-xr-xsmoketest/scripts/cli/test_service_snmp.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py2
-rwxr-xr-xsmoketest/scripts/cli/test_service_upnp.py4
-rwxr-xr-xsmoketest/scripts/cli/test_service_webproxy.py4
-rwxr-xr-xsmoketest/scripts/cli/test_system_flow-accounting.py2
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py5
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py41
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py2
-rwxr-xr-xsmoketest/scripts/cli/test_zone_policy.py4
-rwxr-xr-xsrc/conf_mode/arp.py108
-rwxr-xr-xsrc/conf_mode/containers.py7
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py7
-rwxr-xr-xsrc/conf_mode/dhcp_server.py4
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py7
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py35
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py6
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py2
-rwxr-xr-xsrc/conf_mode/firewall.py5
-rwxr-xr-xsrc/conf_mode/host_name.py5
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py3
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py8
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py8
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py2
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py7
-rwxr-xr-xsrc/conf_mode/interfaces-geneve.py28
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py6
-rwxr-xr-xsrc/conf_mode/interfaces-loopback.py2
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py2
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py17
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py37
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py12
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py4
-rwxr-xr-xsrc/conf_mode/interfaces-vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py15
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py10
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py8
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py28
-rwxr-xr-xsrc/conf_mode/lldp.py7
-rwxr-xr-xsrc/conf_mode/nat.py7
-rwxr-xr-xsrc/conf_mode/nat66.py7
-rwxr-xr-xsrc/conf_mode/policy-route.py3
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py17
-rwxr-xr-xsrc/conf_mode/protocols_static.py5
-rwxr-xr-xsrc/conf_mode/salt-minion.py75
-rwxr-xr-xsrc/conf_mode/snmp.py5
-rwxr-xr-xsrc/conf_mode/tftp_server.py5
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py4
-rwxr-xr-xsrc/etc/ppp/ip-up.d/99-vyos-pppoe-callback20
-rwxr-xr-xsrc/migration-scripts/interfaces/25-to-2654
-rwxr-xr-xsrc/op_mode/generate_ovpn_client_file.py4
-rwxr-xr-xsrc/op_mode/restart_frr.py3
-rwxr-xr-xsrc/op_mode/show_dhcp.py3
-rwxr-xr-xsrc/op_mode/show_dhcpv6.py3
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py17
-rwxr-xr-xsrc/services/vyos-hostsd8
142 files changed, 1843 insertions, 1168 deletions
diff --git a/Makefile b/Makefile
index 1abc79ab3..3e838540c 100644
--- a/Makefile
+++ b/Makefile
@@ -77,7 +77,18 @@ vyxdp:
$(MAKE) -C $(XDP_DIR)
.PHONY: all
-all: clean interface_definitions op_mode_definitions test j2lint vyshim
+all: clean interface_definitions op_mode_definitions check test j2lint vyshim
+
+.PHONY: check
+.ONESHELL:
+check:
+ @echo "Checking which CLI scripts are not enabled to work with vyos-configd..."
+ @for file in `ls src/conf_mode -I__pycache__`
+ do
+ if ! grep -q $$file data/configd-include.json; then
+ echo "* $$file"
+ fi
+ done
.PHONY: clean
clean:
diff --git a/data/configd-include.json b/data/configd-include.json
index b77d48001..2c3c389d6 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -1,9 +1,12 @@
[
+"arp.py",
"bcast_relay.py",
"conntrack.py",
"conntrack_sync.py",
"dhcp_relay.py",
+"dhcp_server.py",
"dhcpv6_relay.py",
+"dhcpv6_server.py",
"dns_forwarding.py",
"dynamic_dns.py",
"flow_accounting_conf.py",
@@ -24,6 +27,7 @@
"interfaces-pppoe.py",
"interfaces-pseudo-ethernet.py",
"interfaces-tunnel.py",
+"interfaces-vti.py",
"interfaces-vxlan.py",
"interfaces-wireguard.py",
"interfaces-wireless.py",
diff --git a/data/templates/dhcp-client/daemon-options.j2 b/data/templates/dhcp-client/daemon-options.j2
new file mode 100644
index 000000000..b21ad08ab
--- /dev/null
+++ b/data/templates/dhcp-client/daemon-options.j2
@@ -0,0 +1,4 @@
+### Autogenerated by interface.py ###
+{% set if_metric = '-e IF_METRIC=' ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined else '' %}
+DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ ifname }}.conf -pf /var/lib/dhcp/dhclient_{{ ifname }}.pid -lf /var/lib/dhcp/dhclient_{{ ifname }}.leases {{ if_metric }} {{ ifname }}"
+
diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl
deleted file mode 100644
index 5b3bff73f..000000000
--- a/data/templates/dhcp-client/daemon-options.tmpl
+++ /dev/null
@@ -1,4 +0,0 @@
-### Autogenerated by interface.py ###
-
-DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ ifname }}.conf -pf /var/lib/dhcp/dhclient_{{ ifname }}.pid -lf /var/lib/dhcp/dhclient_{{ ifname }}.leases{{" -e IF_METRIC=" ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined }} {{ ifname }}"
-
diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.j2
index 83fb93dc1..cc5ddf09c 100644
--- a/data/templates/dhcp-client/ipv4.tmpl
+++ b/data/templates/dhcp-client/ipv4.j2
@@ -8,12 +8,12 @@ initial-interval 2;
interface "{{ ifname }}" {
send host-name "{{ dhcp_options.host_name }}";
{% if dhcp_options.client_id is vyos_defined %}
-{% set client_id = dhcp_options.client_id %}
+{% set client_id = dhcp_options.client_id %}
{# Use HEX representation of client-id as it is send in MAC-address style using hex characters. If not HEX, use double quotes ASCII format #}
-{% if not dhcp_options.client_id.split(':') | length >= 5 %}
-{% set client_id = '"' + dhcp_options.client_id + '"' %}
-{% endif %}
- send dhcp-client-identifier {{ client_id }};
+{% if not dhcp_options.client_id.split(':') | length >= 5 %}
+{% set client_id = '"' + dhcp_options.client_id + '"' %}
+{% endif %}
+ send dhcp-client-identifier {{ client_id }};
{% endif %}
{% if dhcp_options.vendor_class_id is vyos_defined %}
send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}";
diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.j2
index 085cfe5a9..e136b1789 100644
--- a/data/templates/dhcp-client/ipv6.tmpl
+++ b/data/templates/dhcp-client/ipv6.j2
@@ -8,53 +8,53 @@ interface {{ ifname }} {
{% if address is vyos_defined and 'dhcpv6' in address %}
request domain-name-servers;
request domain-name;
-{% if dhcpv6_options.parameters_only is vyos_defined %}
+{% if dhcpv6_options.parameters_only is vyos_defined %}
information-only;
-{% endif %}
-{% if dhcpv6_options.temporary is not vyos_defined %}
+{% endif %}
+{% if dhcpv6_options.temporary is not vyos_defined %}
send ia-na 0; # non-temporary address
-{% endif %}
-{% if dhcpv6_options.rapid_commit is vyos_defined %}
+{% endif %}
+{% if dhcpv6_options.rapid_commit is vyos_defined %}
send rapid-commit; # wait for immediate reply instead of advertisements
-{% endif %}
+{% endif %}
{% endif %}
{% if dhcpv6_options.pd is vyos_defined %}
-{% for pd in dhcpv6_options.pd %}
+{% for pd in dhcpv6_options.pd %}
send ia-pd {{ pd }}; # prefix delegation #{{ pd }}
-{% endfor %}
+{% endfor %}
{% endif %}
};
{% if address is vyos_defined and 'dhcpv6' in address %}
-{% if dhcpv6_options.temporary is not vyos_defined %}
+{% if dhcpv6_options.temporary is not vyos_defined %}
id-assoc na 0 {
# Identity association for non temporary address
};
-{% endif %}
+{% endif %}
{% endif %}
{% if dhcpv6_options.pd is vyos_defined %}
-{% for pd, pd_config in dhcpv6_options.pd.items() %}
+{% for pd, pd_config in dhcpv6_options.pd.items() %}
id-assoc pd {{ pd }} {
{# length got a default value #}
prefix ::/{{ pd_config.length }} infinity;
-{% set sla_len = 64 - pd_config.length|int %}
-{% set count = namespace(value=0) %}
-{% for interface, interface_config in pd_config.interface.items() if pd_config.interface is vyos_defined %}
+{% set sla_len = 64 - pd_config.length | int %}
+{% set count = namespace(value=0) %}
+{% for interface, interface_config in pd_config.interface.items() if pd_config.interface is vyos_defined %}
prefix-interface {{ interface }} {
sla-len {{ sla_len }};
-{% if interface_config.sla_id is vyos_defined %}
+{% if interface_config.sla_id is vyos_defined %}
sla-id {{ interface_config.sla_id }};
-{% else %}
+{% else %}
sla-id {{ count.value }};
-{% endif %}
-{% if interface_config.address is vyos_defined %}
+{% endif %}
+{% if interface_config.address is vyos_defined %}
ifid {{ interface_config.address }};
-{% endif %}
+{% endif %}
};
-{% set count.value = count.value + 1 %}
-{% endfor %}
+{% set count.value = count.value + 1 %}
+{% endfor %}
};
-{% endfor %}
+{% endfor %}
{% endif %}
diff --git a/data/templates/dhcp-relay/dhcrelay.conf.tmpl b/data/templates/dhcp-relay/dhcrelay.conf.j2
index 11710bd8e..11710bd8e 100644
--- a/data/templates/dhcp-relay/dhcrelay.conf.tmpl
+++ b/data/templates/dhcp-relay/dhcrelay.conf.j2
diff --git a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl b/data/templates/dhcp-relay/dhcrelay6.conf.j2
index 1fd5de18c..6365346b4 100644
--- a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl
+++ b/data/templates/dhcp-relay/dhcrelay6.conf.j2
@@ -3,18 +3,18 @@
{# upstream_interface is mandatory so it's always present #}
{% set upstream = namespace(value='') %}
{% for interface, config in upstream_interface.items() %}
-{% for address in config.address %}
-{% set upstream.value = upstream.value ~ '-u ' ~ address ~ '%' ~ interface ~ ' ' %}
-{% endfor %}
+{% for address in config.address %}
+{% set upstream.value = upstream.value ~ '-u ' ~ address ~ '%' ~ interface ~ ' ' %}
+{% endfor %}
{% endfor %}
{# listen_interface is mandatory so it's always present #}
{% set listen = namespace(value='') %}
{% for interface, config in listen_interface.items() %}
-{% if config.address is vyos_defined %}
-{% set listen.value = listen.value ~ '-l ' ~ config.address ~ '%' ~ interface ~ ' ' %}
-{% else %}
-{% set listen.value = listen.value ~ '-l ' ~ interface ~ ' ' %}
-{% endif %}
+{% if config.address is vyos_defined %}
+{% set listen.value = listen.value ~ '-l ' ~ config.address ~ '%' ~ interface ~ ' ' %}
+{% else %}
+{% set listen.value = listen.value ~ '-l ' ~ interface ~ ' ' %}
+{% endif %}
{% endfor %}
OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is vyos_defined }}"
diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.j2
index efc144a1e..4c2da0aa5 100644
--- a/data/templates/dhcp-server/dhcpd.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpd.conf.j2
@@ -23,24 +23,33 @@ option rfc3442-static-route code 121 = array of integer 8;
option windows-static-route code 249 = array of integer 8;
option wpad-url code 252 = text;
+# Vendor specific options - Ubiquiti Networks
+option space ubnt;
+option ubnt.unifi-controller code 1 = ip-address;
+class "ubnt" {
+ match if substring (option vendor-class-identifier , 0, 4) = "ubnt";
+ option vendor-class-identifier "ubnt";
+ vendor-option-space ubnt;
+}
+
{% if global_parameters is vyos_defined %}
# The following {{ global_parameters | length }} line(s) have been added as
# global-parameters in the CLI and have not been validated !!!
-{% for parameter in global_parameters %}
+{% for parameter in global_parameters %}
{{ parameter }}
-{% endfor %}
+{% endfor %}
{% endif %}
{% if failover is vyos_defined %}
# DHCP failover configuration
failover peer "{{ failover.name }}" {
-{% if failover.status == 'primary' %}
+{% if failover.status == 'primary' %}
primary;
mclt 1800;
split 128;
-{% elif failover.status == 'secondary' %}
+{% elif failover.status == 'secondary' %}
secondary;
-{% endif %}
+{% endif %}
address {{ failover.source_address }};
port 647;
peer address {{ failover.remote }};
@@ -53,170 +62,173 @@ failover peer "{{ failover.name }}" {
{% if listen_address is vyos_defined %}
# DHCP server serving relay subnet, we need a connector to the real world
-{% for address in listen_address %}
+{% for address in listen_address %}
# Connected subnet statement for listen-address {{ address }}
subnet {{ address | network_from_ipv4 }} netmask {{ address | netmask_from_ipv4 }} { }
-{% endfor %}
+{% endfor %}
{% endif %}
# Shared network configration(s)
{% if shared_network_name is vyos_defined %}
-{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
+{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
shared-network {{ network }} {
-{% if network_config.authoritative is vyos_defined %}
+{% if network_config.authoritative is vyos_defined %}
authoritative;
-{% endif %}
-{% if network_config.name_server is vyos_defined %}
+{% endif %}
+{% if network_config.name_server is vyos_defined %}
option domain-name-servers {{ network_config.name_server | join(', ') }};
-{% endif %}
-{% if network_config.domain_name is vyos_defined %}
+{% endif %}
+{% if network_config.domain_name is vyos_defined %}
option domain-name "{{ network_config.domain_name }}";
-{% endif %}
-{% if network_config.domain_search is vyos_defined %}
+{% endif %}
+{% if network_config.domain_search is vyos_defined %}
option domain-search "{{ network_config.domain_search | join('", "') }}";
-{% endif %}
-{% if network_config.ntp_server is vyos_defined %}
+{% endif %}
+{% if network_config.ntp_server is vyos_defined %}
option ntp-servers {{ network_config.ntp_server | join(', ') }};
-{% endif %}
-{% if network_config.ping_check is vyos_defined %}
+{% endif %}
+{% if network_config.ping_check is vyos_defined %}
ping-check true;
-{% endif %}
-{% if network_config.shared_network_parameters is vyos_defined %}
+{% endif %}
+{% if network_config.shared_network_parameters is vyos_defined %}
# The following {{ network_config.shared_network_parameters | length }} line(s)
# were added as shared-network-parameters in the CLI and have not been validated
-{% for parameter in network_config.shared_network_parameters %}
+{% for parameter in network_config.shared_network_parameters %}
{{ parameter }}
-{% endfor %}
-{% endif %}
-{% if network_config.subnet is vyos_defined %}
-{% for subnet, subnet_config in network_config.subnet.items() %}
-{% if subnet_config.description is vyos_defined %}
- # {{ subnet_config.description }}
+{% endfor %}
{% endif %}
+{% if network_config.subnet is vyos_defined %}
+{% for subnet, subnet_config in network_config.subnet.items() %}
+{% if subnet_config.description is vyos_defined %}
+ # {{ subnet_config.description }}
+{% endif %}
subnet {{ subnet | address_from_cidr }} netmask {{ subnet | netmask_from_cidr }} {
-{% if subnet_config.name_server is vyos_defined %}
+{% if subnet_config.name_server is vyos_defined %}
option domain-name-servers {{ subnet_config.name_server | join(', ') }};
-{% endif %}
-{% if subnet_config.domain_name is vyos_defined %}
+{% endif %}
+{% if subnet_config.domain_name is vyos_defined %}
option domain-name "{{ subnet_config.domain_name }}";
-{% endif %}
-{% if subnet_config.domain_search is vyos_defined %}
+{% endif %}
+{% if subnet_config.domain_search is vyos_defined %}
option domain-search "{{ subnet_config.domain_search | join('", "') }}";
-{% endif %}
-{% if subnet_config.ntp_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.ntp_server is vyos_defined %}
option ntp-servers {{ subnet_config.ntp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.pop_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.pop_server is vyos_defined %}
option pop-server {{ subnet_config.pop_server | join(', ') }};
-{% endif %}
-{% if subnet_config.smtp_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.smtp_server is vyos_defined %}
option smtp-server {{ subnet_config.smtp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.time_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.time_server is vyos_defined %}
option time-servers {{ subnet_config.time_server | join(', ') }};
-{% endif %}
-{% if subnet_config.wins_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.wins_server is vyos_defined %}
option netbios-name-servers {{ subnet_config.wins_server | join(', ') }};
-{% endif %}
-{% if subnet_config.static_route is vyos_defined %}
-{% set static_default_route = '' %}
-{% if subnet_config.default_router is vyos_defined %}
-{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %}
-{% endif %}
-{% if subnet_config.static_route is vyos_defined %}
-{% set rfc3442_routes = [] %}
-{% for route, route_options in subnet_config.static_route.items() %}
-{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %}
-{% endfor %}
+{% endif %}
+{% if subnet_config.static_route is vyos_defined %}
+{% set static_default_route = '' %}
+{% if subnet_config.default_router is vyos_defined %}
+{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %}
+{% endif %}
+{% if subnet_config.static_route is vyos_defined %}
+{% set rfc3442_routes = [] %}
+{% for route, route_options in subnet_config.static_route.items() %}
+{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %}
+{% endfor %}
option rfc3442-static-route {{ rfc3442_routes | join(', ') }}{{ static_default_route }};
option windows-static-route {{ rfc3442_routes | join(', ') }};
-{% endif %}
-{% endif %}
-{% if subnet_config.ip_forwarding is vyos_defined %}
+{% endif %}
+{% endif %}
+{% if subnet_config.ip_forwarding is vyos_defined %}
option ip-forwarding true;
-{% endif %}
-{% if subnet_config.default_router is vyos_defined %}
+{% endif %}
+{% if subnet_config.default_router is vyos_defined %}
option routers {{ subnet_config.default_router }};
-{% endif %}
-{% if subnet_config.server_identifier is vyos_defined %}
+{% endif %}
+{% if subnet_config.server_identifier is vyos_defined %}
option dhcp-server-identifier {{ subnet_config.server_identifier }};
-{% endif %}
-{% if subnet_config.subnet_parameters is vyos_defined %}
+{% endif %}
+{% if subnet_config.subnet_parameters is vyos_defined %}
# The following {{ subnet_config.subnet_parameters | length }} line(s) were added as
# subnet-parameters in the CLI and have not been validated!!!
-{% for parameter in subnet_config.subnet_parameters %}
+{% for parameter in subnet_config.subnet_parameters %}
{{ parameter }}
-{% endfor %}
-{% endif %}
-{% if subnet_config.tftp_server_name is vyos_defined %}
+{% endfor %}
+{% endif %}
+{% if subnet_config.tftp_server_name is vyos_defined %}
option tftp-server-name "{{ subnet_config.tftp_server_name }}";
-{% endif %}
-{% if subnet_config.bootfile_name is vyos_defined %}
+{% endif %}
+{% if subnet_config.bootfile_name is vyos_defined %}
option bootfile-name "{{ subnet_config.bootfile_name }}";
filename "{{ subnet_config.bootfile_name }}";
-{% endif %}
-{% if subnet_config.bootfile_server is vyos_defined %}
+{% endif %}
+{% if subnet_config.bootfile_server is vyos_defined %}
next-server {{ subnet_config.bootfile_server }};
-{% endif %}
-{% if subnet_config.bootfile_size is vyos_defined %}
+{% endif %}
+{% if subnet_config.bootfile_size is vyos_defined %}
option boot-size {{ subnet_config.bootfile_size }};
-{% endif %}
-{% if subnet_config.time_offset is vyos_defined %}
+{% endif %}
+{% if subnet_config.time_offset is vyos_defined %}
option time-offset {{ subnet_config.time_offset }};
-{% endif %}
-{% if subnet_config.wpad_url is vyos_defined %}
+{% endif %}
+{% if subnet_config.wpad_url is vyos_defined %}
option wpad-url "{{ subnet_config.wpad_url }}";
-{% endif %}
-{% if subnet_config.client_prefix_length is vyos_defined %}
+{% endif %}
+{% if subnet_config.client_prefix_length is vyos_defined %}
option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }};
-{% endif %}
-{% if subnet_config.lease is vyos_defined %}
+{% endif %}
+{% if subnet_config.lease is vyos_defined %}
default-lease-time {{ subnet_config.lease }};
max-lease-time {{ subnet_config.lease }};
-{% endif %}
-{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %}
+{% endif %}
+{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %}
ping-check true;
-{% endif %}
-{% if subnet_config.static_mapping is vyos_defined %}
-{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
+{% endif %}
+{% if subnet_config.static_mapping is vyos_defined %}
+{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
host {{ host | replace('_','-') if host_decl_name is vyos_defined else network | replace('_','-') ~ '_' ~ host | replace('_','-') }} {
-{% if host_config.ip_address is vyos_defined %}
+{% if host_config.ip_address is vyos_defined %}
fixed-address {{ host_config.ip_address }};
-{% endif %}
+{% endif %}
hardware ethernet {{ host_config.mac_address }};
-{% if host_config.static_mapping_parameters is vyos_defined %}
+{% if host_config.static_mapping_parameters is vyos_defined %}
# The following {{ host_config.static_mapping_parameters | length }} line(s) were added
# as static-mapping-parameters in the CLI and have not been validated
-{% for parameter in host_config.static_mapping_parameters %}
+{% for parameter in host_config.static_mapping_parameters %}
{{ parameter }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
-{% endfor %}
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
+{% endfor %}
+{% endif %}
+{% if subnet_config.vendor_option.ubiquiti.unifi_controller is vyos_defined %}
+ option ubnt.unifi-controller {{ subnet_config.vendor_option.ubiquiti.unifi_controller }};
+{% endif %}
+{% if subnet_config.range is vyos_defined %}
{# pool configuration can only be used if there follows a range option #}
pool {
-{% endif %}
-{% if subnet_config.enable_failover is vyos_defined %}
+{% endif %}
+{% if subnet_config.enable_failover is vyos_defined %}
failover peer "{{ failover.name }}";
deny dynamic bootp clients;
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
-{% for range, range_options in subnet_config.range.items() %}
+{% endif %}
+{% if subnet_config.range is vyos_defined %}
+{% for range, range_options in subnet_config.range.items() %}
range {{ range_options.start }} {{ range_options.stop }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.range is vyos_defined %}
+{% endfor %}
+{% endif %}
+{% if subnet_config.range is vyos_defined %}
{# pool configuration can only be used if there follows a range option #}
}
-{% endif %}
+{% endif %}
}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
on commit {
set shared-networkname = "{{ network }}";
-{% if hostfile_update is vyos_defined %}
+{% if hostfile_update is vyos_defined %}
set ClientIp = binary-to-ascii(10, 8, ".", leased-address);
set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "empty_hostname");
@@ -226,9 +238,9 @@ shared-network {{ network }} {
} else {
log(concat("Hostname is not defined for client with IP: ", ClientIP, " MAC: ", ClientMac));
}
-{% endif %}
+{% endif %}
}
}
-{% endfor %}
+{% endfor %}
{% endif %}
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.j2 b/data/templates/dhcp-server/dhcpdv6.conf.j2
new file mode 100644
index 000000000..5c3471316
--- /dev/null
+++ b/data/templates/dhcp-server/dhcpdv6.conf.j2
@@ -0,0 +1,132 @@
+### Autogenerated by dhcpv6_server.py ###
+
+# For options please consult the following website:
+# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
+
+log-facility local7;
+{% if preference is vyos_defined %}
+option dhcp6.preference {{ preference }};
+{% endif %}
+
+{% if global_parameters.name_server is vyos_defined %}
+option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }};
+{% endif %}
+
+# Vendor specific options - Cisco
+option space cisco code width 2 length width 2;
+option cisco.tftp-servers code 1 = array of ip6-address;
+option vsio.cisco code 9 = encapsulate cisco;
+
+# Shared network configration(s)
+{% if shared_network_name is vyos_defined %}
+{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
+shared-network {{ network }} {
+{% if network_config.common_options is vyos_defined %}
+{% if network_config.common_options.info_refresh_time is vyos_defined %}
+ option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
+{% endif %}
+{% if network_config.common_options.domain_search is vyos_defined %}
+ option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}";
+{% endif %}
+{% if network_config.common_options.name_server is vyos_defined %}
+ option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }};
+{% endif %}
+{% endif %}
+{% if network_config.subnet is vyos_defined %}
+{% for subnet, subnet_config in network_config.subnet.items() %}
+ subnet6 {{ subnet }} {
+{% if subnet_config.address_range is vyos_defined %}
+{% if subnet_config.address_range.prefix is vyos_defined %}
+{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %}
+ range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }};
+{% endfor %}
+{% endif %}
+{% if subnet_config.address_range.start is vyos_defined %}
+{% for address, address_config in subnet_config.address_range.start.items() %}
+ range6 {{ address }} {{ address_config.stop }};
+{% endfor %}
+{% endif %}
+{% endif %}
+{% if subnet_config.domain_search is vyos_defined %}
+ option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}";
+{% endif %}
+{% if subnet_config.lease_time is vyos_defined %}
+{% if subnet_config.lease_time.default is vyos_defined %}
+ default-lease-time {{ subnet_config.lease_time.default }};
+{% endif %}
+{% if subnet_config.lease_time.maximum is vyos_defined %}
+ max-lease-time {{ subnet_config.lease_time.maximum }};
+{% endif %}
+{% if subnet_config.lease_time.minimum is vyos_defined %}
+ min-lease-time {{ subnet_config.lease_time.minimum }};
+{% endif %}
+{% endif %}
+{% if subnet_config.name_server is vyos_defined %}
+ option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }};
+{% endif %}
+{% if subnet_config.nis_domain is vyos_defined %}
+ option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}";
+{% endif %}
+{% if subnet_config.nis_server is vyos_defined %}
+ option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }};
+{% endif %}
+{% if subnet_config.nisplus_domain is vyos_defined %}
+ option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}";
+{% endif %}
+{% if subnet_config.nisplus_server is vyos_defined %}
+ option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }};
+{% endif %}
+{% if subnet_config.sip_server is vyos_defined %}
+{% set server_ip = [] %}
+{% set server_fqdn = [] %}
+{% for address in subnet_config.sip_server %}
+{% if address | is_ipv6 %}
+{% set server_ip = server_ip.append(address) %}
+{% else %}
+{% set server_fqdn = server_fqdn.append(address) %}
+{% endif %}
+{% endfor %}
+{% if server_ip is vyos_defined and server_ip | length > 0 %}
+ option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }};
+{% endif %}
+{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %}
+ option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}";
+{% endif %}
+{% endif %}
+{% if subnet_config.sntp_server is vyos_defined %}
+ option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }};
+{% endif %}
+{% if subnet_config.prefix_delegation.start is vyos_defined %}
+{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %}
+ prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }};
+{% endfor %}
+{% endif %}
+{% if subnet_config.static_mapping is vyos_defined %}
+
+ # begin configuration of static client mappings
+{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
+ host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} {
+{% if host_config.identifier is vyos_defined %}
+ host-identifier option dhcp6.client-id {{ host_config.identifier }};
+{% endif %}
+{% if host_config.ipv6_address is vyos_defined %}
+ fixed-address6 {{ host_config.ipv6_address }};
+{% endif %}
+{% if host_config.ipv6_prefix is vyos_defined %}
+ fixed-prefix6 {{ host_config.ipv6_prefix }};
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
+{% if subnet_config.vendor_option.cisco.tftp_server is vyos_defined %}
+ option cisco.tftp-servers {{ subnet_config.vendor_option.cisco.tftp_server | join(', ') }};
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
+ on commit {
+ set shared-networkname = "{{ network }}";
+ }
+}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl
deleted file mode 100644
index 1a55668e1..000000000
--- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl
+++ /dev/null
@@ -1,124 +0,0 @@
-### Autogenerated by dhcpv6_server.py ###
-
-# For options please consult the following website:
-# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html
-
-log-facility local7;
-{% if preference is vyos_defined %}
-option dhcp6.preference {{ preference }};
-{% endif %}
-
-{% if global_parameters.name_server is vyos_defined %}
-option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }};
-{% endif %}
-
-# Shared network configration(s)
-{% if shared_network_name is vyos_defined %}
-{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %}
-shared-network {{ network }} {
-{% if network_config.common_options is vyos_defined %}
-{% if network_config.common_options.info_refresh_time is vyos_defined %}
- option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }};
-{% endif %}
-{% if network_config.common_options.domain_search is vyos_defined %}
- option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}";
-{% endif %}
-{% if network_config.common_options.name_server is vyos_defined %}
- option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }};
-{% endif %}
-{% endif %}
-{% if network_config.subnet is vyos_defined %}
-{% for subnet, subnet_config in network_config.subnet.items() %}
- subnet6 {{ subnet }} {
-{% if subnet_config.address_range is vyos_defined %}
-{% if subnet_config.address_range.prefix is vyos_defined %}
-{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %}
- range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.address_range.start is vyos_defined %}
-{% for address, address_config in subnet_config.address_range.start.items() %}
- range6 {{ address }} {{ address_config.stop }};
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if subnet_config.domain_search is vyos_defined %}
- option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}";
-{% endif %}
-{% if subnet_config.lease_time is vyos_defined %}
-{% if subnet_config.lease_time.default is vyos_defined %}
- default-lease-time {{ subnet_config.lease_time.default }};
-{% endif %}
-{% if subnet_config.lease_time.maximum is vyos_defined %}
- max-lease-time {{ subnet_config.lease_time.maximum }};
-{% endif %}
-{% if subnet_config.lease_time.minimum is vyos_defined %}
- min-lease-time {{ subnet_config.lease_time.minimum }};
-{% endif %}
-{% endif %}
-{% if subnet_config.name_server is vyos_defined %}
- option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }};
-{% endif %}
-{% if subnet_config.nis_domain is vyos_defined %}
- option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}";
-{% endif %}
-{% if subnet_config.nis_server is vyos_defined %}
- option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }};
-{% endif %}
-{% if subnet_config.nisplus_domain is vyos_defined %}
- option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}";
-{% endif %}
-{% if subnet_config.nisplus_server is vyos_defined %}
- option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }};
-{% endif %}
-{% if subnet_config.sip_server is vyos_defined %}
-{% set server_ip = [] %}
-{% set server_fqdn = [] %}
-{% for address in subnet_config.sip_server %}
-{% if address | is_ipv6 %}
-{% set server_ip = server_ip.append(address) %}
-{% else %}
-{% set server_fqdn = server_fqdn.append(address) %}
-{% endif %}
-{% endfor %}
-{% if server_ip is vyos_defined and server_ip | length > 0 %}
- option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }};
-{% endif %}
-{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %}
- option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}";
-{% endif %}
-{% endif %}
-{% if subnet_config.sntp_server is vyos_defined %}
- option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }};
-{% endif %}
-{% if subnet_config.prefix_delegation.start is vyos_defined %}
-{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %}
- prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }};
-{% endfor %}
-{% endif %}
-{% if subnet_config.static_mapping is vyos_defined %}
-
- # begin configuration of static client mappings
-{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %}
- host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} {
-{% if host_config.identifier is vyos_defined %}
- host-identifier option dhcp6.client-id {{ host_config.identifier }};
-{% endif %}
-{% if host_config.ipv6_address is vyos_defined %}
- fixed-address6 {{ host_config.ipv6_address }};
-{% endif %}
-{% if host_config.ipv6_prefix is vyos_defined %}
- fixed-prefix6 {{ host_config.ipv6_prefix }};
-{% endif %}
- }
-{% endfor %}
-{% endif %}
- }
-{% endfor %}
-{% endif %}
- on commit {
- set shared-networkname = "{{ network }}";
- }
-}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.j2
index 385bef94b..c1950e1bc 100644
--- a/data/templates/dns-forwarding/recursor.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.conf.j2
@@ -1,3 +1,4 @@
+{# j2lint: disable=single-statement-per-line #}
### Autogenerated by dns_forwarding.py ###
# XXX: pdns recursor doesn't like whitespace near entry separators,
diff --git a/data/templates/dns-forwarding/recursor.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.conf.lua.j2
index e2506238d..e2506238d 100644
--- a/data/templates/dns-forwarding/recursor.conf.lua.tmpl
+++ b/data/templates/dns-forwarding/recursor.conf.lua.j2
diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2
index 96cbc35a5..de3269e47 100644
--- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2
@@ -1,3 +1,4 @@
+{# j2lint: disable=operator-enclosed-by-spaces #}
# Autogenerated by VyOS (vyos-hostsd)
# Do not edit, your changes will get overwritten
@@ -7,11 +8,11 @@
{# the order of tags, then by the order of nameservers within that tag #}
{% set n = namespace(dot_zone_ns='') %}
{% for tag in name_server_tags_recursor %}
-{% set ns = '' %}
-{% if tag in name_servers %}
-{% set ns = ns + name_servers[tag]|join(', ') %}
-{% set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %}
-{% endif %}
+{% set ns = '' %}
+{% if tag in name_servers %}
+{% set ns = ns + name_servers[tag] | join(', ') %}
+{% set n.dot_zone_ns = (n.dot_zone_ns, ns) | join(', ') if n.dot_zone_ns != '' else ns %}
+{% endif %}
# {{ tag }}: {{ ns }}
{% endfor %}
@@ -21,8 +22,8 @@
{% if forward_zones is vyos_defined %}
# zones added via 'service dns forwarding domain'
-{% for zone, zonedata in forward_zones.items() %}
+{% for zone, zonedata in forward_zones.items() %}
{{ "+" if zonedata.recursion_desired is vyos_defined }}{{ zone | replace('_', '-') }}={{ zonedata.server | join(', ') }}
-{% endfor %}
+{% endfor %}
{% endif %}
diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.j2
index 04fb72121..987c7de1f 100644
--- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
+++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.j2
@@ -3,28 +3,28 @@
{% if hosts %}
-- from 'system static-host-mapping' and DHCP server
-{% for tag, taghosts in hosts.items() %}
-{% for host, hostprops in taghosts.items() %}
+{% for tag, taghosts in hosts.items() %}
+{% for host, hostprops in taghosts.items() %}
addNTA("{{ host }}.", "{{ tag }}")
-{% for a in hostprops['aliases'] %}
+{% for a in hostprops['aliases'] %}
addNTA("{{ a }}.", "{{ tag }} alias")
-{% endfor %}
+{% endfor %}
+{% endfor %}
{% endfor %}
-{% endfor %}
{% endif %}
{% if forward_zones is vyos_defined %}
-- from 'service dns forwarding domain'
-{% for zone, zonedata in forward_zones.items() %}
-{% if zonedata.addnta is vyos_defined %}
+{% for zone, zonedata in forward_zones.items() %}
+{% if zonedata.addnta is vyos_defined %}
addNTA("{{ zone }}", "static")
-{% endif %}
-{% endfor %}
+{% endif %}
+{% endfor %}
{% endif %}
{% if authoritative_zones is vyos_defined %}
-- from 'service dns forwarding authoritative-domain'
-{% for zone in authoritative_zones %}
+{% for zone in authoritative_zones %}
addNTA("{{ zone }}", "static")
-{% endfor %}
+{% endfor %}
{% endif %}
diff --git a/data/templates/dns-forwarding/recursor.zone.conf.tmpl b/data/templates/dns-forwarding/recursor.zone.conf.j2
index 758871bef..25193c2ec 100644
--- a/data/templates/dns-forwarding/recursor.zone.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.zone.conf.j2
@@ -1,7 +1,6 @@
;
; Autogenerated by dns_forwarding.py
;
-;
{% for r in records %}
-{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }}
+{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }}
{% endfor %}
diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2
new file mode 100644
index 000000000..3c2d17cbb
--- /dev/null
+++ b/data/templates/dynamic-dns/ddclient.conf.j2
@@ -0,0 +1,51 @@
+### Autogenerated by dynamic_dns.py ###
+daemon=1m
+syslog=yes
+ssl=yes
+
+{% if interface is vyos_defined %}
+{% for iface, iface_config in interface.items() %}
+# ddclient configuration for interface "{{ iface }}"
+{% if iface_config.use_web is vyos_defined %}
+{% set web_skip = ", web-skip='" ~ iface_config.use_web.skip ~ "'" if iface_config.use_web.skip is vyos_defined else '' %}
+use=web, web='{{ iface_config.use_web.url }}'{{ web_skip }}
+{% else %}
+{{ 'usev6=if' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }}
+{% endif %}
+
+{% if iface_config.rfc2136 is vyos_defined %}
+{% for rfc2136, config in iface_config.rfc2136.items() %}
+{% for dns_record in config.record if config.record is vyos_defined %}
+# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }}
+server={{ config.server }}
+protocol=nsupdate
+password={{ config.key }}
+ttl={{ config.ttl }}
+zone={{ config.zone }}
+{{ dns_record }}
+
+{% endfor %}
+{% endfor %}
+{% endif %}
+
+{% if iface_config.service is vyos_defined %}
+{% for service, config in iface_config.service.items() %}
+{% for dns_record in config.host_name %}
+# DynDNS provider configuration for {{ service }}, {{ dns_record }}
+protocol={{ config.protocol }},
+max-interval=28d,
+login={{ config.login }},
+password='{{ config.password }}',
+{% if config.server is vyos_defined %}
+server={{ config.server }},
+{% endif %}
+{% if config.zone is vyos_defined %}
+zone={{ config.zone }},
+{% endif %}
+{{ dns_record }}
+
+{% endfor %}
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl
deleted file mode 100644
index ee55c9fa6..000000000
--- a/data/templates/dynamic-dns/ddclient.conf.tmpl
+++ /dev/null
@@ -1,51 +0,0 @@
-### Autogenerated by dynamic_dns.py ###
-daemon=1m
-syslog=yes
-ssl=yes
-
-{% if interface is vyos_defined %}
-{% for iface, iface_config in interface.items() %}
-# ddclient configuration for interface "{{ iface }}"
-{% if iface_config.use_web is vyos_defined %}
-{% set web_skip = ", web-skip='" ~ iface_config.use_web.skip ~ "'" if iface_config.use_web.skip is vyos_defined else '' %}
-use=web, web='{{ iface_config.use_web.url }}'{{ web_skip }}
-{% else %}
-{{ 'usev6=if' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }}
-{% endif %}
-
-{% if iface_config.rfc2136 is vyos_defined %}
-{% for rfc2136, config in iface_config.rfc2136.items() %}
-{% for dns_record in config.record if config.record is vyos_defined %}
-# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }}
-server={{ config.server }}
-protocol=nsupdate
-password={{ config.key }}
-ttl={{ config.ttl }}
-zone={{ config.zone }}
-{{ dns_record }}
-
-{% endfor %}
-{% endfor %}
-{% endif %}
-
-{% if iface_config.service is vyos_defined %}
-{% for service, config in iface_config.service.items() %}
-{% for dns_record in config.host_name %}
-# DynDNS provider configuration for {{ service }}, {{ dns_record }}
-protocol={{ config.protocol }},
-max-interval=28d,
-login={{ config.login }},
-password='{{ config.password }}',
-{% if config.server is vyos_defined %}
-server={{ config.server }},
-{% endif %}
-{% if config.zone is vyos_defined %}
-zone={{ config.zone }},
-{% endif %}
-{{ dns_record }}
-
-{% endfor %}
-{% endfor %}
-{% endif %}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl
index 922f3dcb4..63aa48c77 100644
--- a/data/templates/firewall/nftables-nat.tmpl
+++ b/data/templates/firewall/nftables-nat.tmpl
@@ -6,14 +6,14 @@
{% set src_addr = 'ip saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %}
{% set dst_addr = 'ip daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %}
{# negated port groups need special treatment, move != in front of { } group #}
-{% if config.source.port is vyos_defined and config.source.port.startswith('!=') %}
-{% set src_port = 'sport != { ' ~ config.source.port.replace('!=','') ~ ' }' %}
+{% if config.source.port is vyos_defined and config.source.port.startswith('!') %}
+{% set src_port = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %}
{% else %}
{% set src_port = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %}
{% endif %}
{# negated port groups need special treatment, move != in front of { } group #}
-{% if config.destination.port is vyos_defined and config.destination.port.startswith('!=') %}
-{% set dst_port = 'dport != { ' ~ config.destination.port.replace('!=','') ~ ' }' %}
+{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %}
+{% set dst_port = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %}
{% else %}
{% set dst_port = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %}
{% endif %}
@@ -138,8 +138,9 @@
{% endif %}
{% endmacro %}
-# Start with clean NAT table
-flush table ip nat
+# Start with clean SNAT and DNAT chains
+flush chain ip nat PREROUTING
+flush chain ip nat POSTROUTING
{% if helper_functions is vyos_defined('remove') %}
{# NAT if going to be disabled - remove rules and targets from nftables #}
{% set base_command = 'delete rule ip raw' %}
@@ -164,6 +165,7 @@ add rule ip raw NAT_CONNTRACK counter accept
#
# Destination NAT rules build up here
#
+add rule ip nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK
{% if destination.rule is vyos_defined %}
{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'PREROUTING') }}
@@ -172,6 +174,7 @@ add rule ip raw NAT_CONNTRACK counter accept
#
# Source NAT rules build up here
#
+add rule ip nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK
{% if source.rule is vyos_defined %}
{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'POSTROUTING') }}
diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2
index 08b2a3dab..589f03c2c 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -20,10 +20,16 @@ vrf {{ vrf }}
{% for interface, interface_config in dhcp.items() %}
{% set next_hop = interface | get_dhcp_router %}
{% if next_hop is vyos_defined %}
-{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.distance }}
+{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.dhcp_options.default_route_distance if interface_config.dhcp_options.default_route_distance is vyos_defined }}
{% endif %}
{% endfor %}
{% endif %}
+{# IPv4 default routes from PPPoE interfaces #}
+{% if pppoe is vyos_defined %}
+{% for interface, interface_config in pppoe.items() %}
+{{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }}
+{% endfor %}
+{% endif %}
{# IPv6 routing #}
{% if route6 is vyos_defined %}
{% for prefix, prefix_config in route6.items() %}
diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl
index b21dce9f0..61af85ed4 100644
--- a/data/templates/ipsec/swanctl/peer.tmpl
+++ b/data/templates/ipsec/swanctl/peer.tmpl
@@ -152,7 +152,7 @@
{% endif %}
}
{% if tunnel_conf.passthrough is vyos_defined %}
- peer_{{ name }}_tunnel_{{ tunnel_id }}_passthough {
+ peer_{{ name }}_tunnel_{{ tunnel_id }}_passthrough {
local_ts = {{ tunnel_conf.passthrough | join(",") }}
remote_ts = {{ tunnel_conf.passthrough | join(",") }}
start_action = trap
diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl
index d3145a500..cf33eec4e 100644
--- a/data/templates/monitoring/telegraf.tmpl
+++ b/data/templates/monitoring/telegraf.tmpl
@@ -1,12 +1,12 @@
# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py
[agent]
- interval = "10s"
+ interval = "15s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
- collection_jitter = "0s"
- flush_interval = "10s"
+ collection_jitter = "5s"
+ flush_interval = "15s"
flush_jitter = "0s"
precision = ""
debug = false
diff --git a/data/templates/openvpn/auth.pw.tmpl b/data/templates/openvpn/auth.pw.j2
index 218121062..218121062 100644
--- a/data/templates/openvpn/auth.pw.tmpl
+++ b/data/templates/openvpn/auth.pw.j2
diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.j2
index 98c8b0273..2e327e4d3 100644
--- a/data/templates/openvpn/client.conf.tmpl
+++ b/data/templates/openvpn/client.conf.j2
@@ -1,30 +1,30 @@
### Autogenerated by interfaces-openvpn.py ###
-{% if ip %}
+{% if ip is vyos_defined %}
ifconfig-push {{ ip[0] }} {{ server_subnet[0] | netmask_from_cidr }}
{% endif %}
{% if push_route is vyos_defined %}
-{% for route in push_route %}
+{% for route in push_route %}
push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}"
-{% endfor %}
+{% endfor %}
{% endif %}
{% if subnet is vyos_defined %}
-{% for network in subnet %}
+{% for network in subnet %}
iroute {{ network | address_from_cidr }} {{ network | netmask_from_cidr }}
-{% endfor %}
+{% endfor %}
{% endif %}
{# ipv6_remote is only set when IPv6 server is enabled #}
-{% if ipv6_remote %}
+{% if ipv6_remote is vyos_defined %}
# IPv6
-{% if ipv6_ip %}
+{% if ipv6_ip is vyos_defined %}
ifconfig-ipv6-push {{ ipv6_ip[0] }} {{ ipv6_remote }}
-{% endif %}
-{% for route6 in ipv6_push_route %}
+{% endif %}
+{% for route6 in ipv6_push_route %}
push "route-ipv6 {{ route6 }}"
-{% endfor %}
-{% for net6 in ipv6_subnet %}
+{% endfor %}
+{% for net6 in ipv6_subnet %}
iroute-ipv6 {{ net6 }}
-{% endfor %}
+{% endfor %}
{% endif %}
{% if disable is vyos_defined %}
disable
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.j2
index f26680fa3..6dd4ef88d 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.j2
@@ -10,9 +10,9 @@ verb 3
dev-type {{ device_type }}
dev {{ ifname }}
persist-key
-{% if protocol == 'tcp-active' %}
+{% if protocol is vyos_defined('tcp-active') %}
proto tcp-client
-{% elif protocol == 'tcp-passive' %}
+{% elif protocol is vyos_defined('tcp-passive') %}
proto tcp-server
{% else %}
proto udp
@@ -30,9 +30,9 @@ lport {{ local_port }}
rport {{ remote_port }}
{% endif %}
{% if remote_host is vyos_defined %}
-{% for remote in remote_host %}
+{% for remote in remote_host %}
remote {{ remote }}
-{% endfor %}
+{% endfor %}
{% endif %}
{% if shared_secret_key is vyos_defined %}
secret /run/openvpn/{{ ifname }}_shared.key
@@ -49,88 +49,88 @@ push "redirect-gateway def1"
compress lzo
{% endif %}
-{% if mode == 'client' %}
+{% if mode is vyos_defined('client') %}
#
# OpenVPN Client mode
#
client
nobind
-{% elif mode == 'server' %}
+{% elif mode is vyos_defined('server') %}
#
# OpenVPN Server mode
#
mode server
tls-server
-{% if server is vyos_defined %}
-{% if server.subnet is vyos_defined %}
-{% if server.topology is vyos_defined('point-to-point') %}
+{% if server is vyos_defined %}
+{% if server.subnet is vyos_defined %}
+{% if server.topology is vyos_defined('point-to-point') %}
topology p2p
-{% elif server.topology is vyos_defined %}
+{% elif server.topology is vyos_defined %}
topology {{ server.topology }}
-{% endif %}
-{% for subnet in server.subnet %}
-{% if subnet | is_ipv4 %}
+{% endif %}
+{% for subnet in server.subnet %}
+{% if subnet | is_ipv4 %}
server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool
{# First ip address is used as gateway. It's allows to use metrics #}
-{% if server.push_route is vyos_defined %}
-{% for route, route_config in server.push_route.items() %}
-{% if route | is_ipv4 %}
-push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}{% if route_config.metric is vyos_defined %} {{ subnet | first_host_address }} {{ route_config.metric }}{% endif %}"
-{% elif route | is_ipv6 %}
+{% if server.push_route is vyos_defined %}
+{% for route, route_config in server.push_route.items() %}
+{% if route | is_ipv4 %}
+push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ subnet | first_host_address ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}"
+{% elif route | is_ipv6 %}
push "route-ipv6 {{ route }}"
-{% endif %}
-{% endfor %}
-{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
{# OpenVPN assigns the first IP address to its local interface so the pool used #}
{# in net30 topology - where each client receives a /30 must start from the second subnet #}
-{% if server.topology is vyos_defined('net30') %}
+{% if server.topology is vyos_defined('net30') %}
ifconfig-pool {{ subnet | inc_ip('4') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }}
-{% else %}
+{% else %}
{# OpenVPN assigns the first IP address to its local interface so the pool must #}
{# start from the second address and end on the last address #}
ifconfig-pool {{ subnet | first_host_address | inc_ip('1') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tun' else '' }}
-{% endif %}
-{% elif subnet | is_ipv6 %}
+{% endif %}
+{% elif subnet | is_ipv6 %}
server-ipv6 {{ subnet }}
+{% endif %}
+{% endfor %}
{% endif %}
-{% endfor %}
-{% endif %}
-{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
+{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %}
ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }}{{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }}
-{% endif %}
-{% if server.max_connections is vyos_defined %}
+{% endif %}
+{% if server.max_connections is vyos_defined %}
max-clients {{ server.max_connections }}
-{% endif %}
-{% if server.client is vyos_defined %}
+{% endif %}
+{% if server.client is vyos_defined %}
client-config-dir /run/openvpn/ccd/{{ ifname }}
+{% endif %}
{% endif %}
-{% endif %}
-keepalive {{ keep_alive.interval }} {{ keep_alive.interval|int * keep_alive.failure_count|int }}
+keepalive {{ keep_alive.interval }} {{ keep_alive.interval | int * keep_alive.failure_count | int }}
management /run/openvpn/openvpn-mgmt-intf unix
-{% if server is vyos_defined %}
-{% if server.reject_unconfigured_clients is vyos_defined %}
+{% if server is vyos_defined %}
+{% if server.reject_unconfigured_clients is vyos_defined %}
ccd-exclusive
-{% endif %}
+{% endif %}
-{% if server.name_server is vyos_defined %}
-{% for nameserver in server.name_server %}
-{% if nameserver | is_ipv4 %}
+{% if server.name_server is vyos_defined %}
+{% for nameserver in server.name_server %}
+{% if nameserver | is_ipv4 %}
push "dhcp-option DNS {{ nameserver }}"
-{% elif nameserver | is_ipv6 %}
+{% elif nameserver | is_ipv6 %}
push "dhcp-option DNS6 {{ nameserver }}"
+{% endif %}
+{% endfor %}
{% endif %}
-{% endfor %}
-{% endif %}
-{% if server.domain_name is vyos_defined %}
+{% if server.domain_name is vyos_defined %}
push "dhcp-option DOMAIN {{ server.domain_name }}"
+{% endif %}
+{% if server.mfa.totp is vyos_defined %}
+{% set totp_config = server.mfa.totp %}
+plugin "{{ plugin_dir }}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifname }}-otp-secrets otp_slop={{ totp_config.slop }} totp_t0={{ totp_config.drift }} totp_step={{ totp_config.step }} totp_digits={{ totp_config.digits }} password_is_cr={{ '1' if totp_config.challenge == 'enable' else '0' }}"
+{% endif %}
{% endif %}
-{% if server.mfa.totp is vyos_defined %}
-{% set totp_config = server.mfa.totp %}
-plugin "{{ plugin_dir}}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifname }}-otp-secrets {{ 'otp_slop=' ~ totp_config.slop }} {{ 'totp_t0=' ~ totp_config.drift }} {{ 'totp_step=' ~ totp_config.step }} {{ 'totp_digits=' ~ totp_config.digits }} password_is_cr={{ '1' if totp_config.challenge == 'enable' else '0' }}"
-{% endif %}
-{% endif %}
{% else %}
#
# OpenVPN site-2-site mode
@@ -138,80 +138,80 @@ plugin "{{ plugin_dir}}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifn
ping {{ keep_alive.interval }}
ping-restart {{ keep_alive.failure_count }}
-{% if device_type == 'tap' %}
-{% if local_address is vyos_defined %}
-{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %}
-{% if laddr_conf.subnet_mask is vyos_defined %}
+{% if device_type == 'tap' %}
+{% if local_address is vyos_defined %}
+{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %}
+{% if laddr_conf.subnet_mask is vyos_defined %}
ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }}
+{% endif %}
+{% endfor %}
{% endif %}
-{% endfor %}
-{% endif %}
-{% else %}
-{% for laddr in local_address if laddr | is_ipv4 %}
-{% for raddr in remote_address if raddr | is_ipv4 %}
+{% else %}
+{% for laddr in local_address if laddr | is_ipv4 %}
+{% for raddr in remote_address if raddr | is_ipv4 %}
ifconfig {{ laddr }} {{ raddr }}
-{% endfor %}
-{% endfor %}
-{% for laddr in local_address if laddr | is_ipv6 %}
-{% for raddr in remote_address if raddr | is_ipv6 %}
+{% endfor %}
+{% endfor %}
+{% for laddr in local_address if laddr | is_ipv6 %}
+{% for raddr in remote_address if raddr | is_ipv6 %}
ifconfig-ipv6 {{ laddr }} {{ raddr }}
-{% endfor %}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endfor %}
+{% endif %}
{% endif %}
{% if tls is vyos_defined %}
# TLS options
-{% if tls.ca_certificate is vyos_defined %}
+{% if tls.ca_certificate is vyos_defined %}
ca /run/openvpn/{{ ifname }}_ca.pem
-{% endif %}
-{% if tls.certificate is vyos_defined %}
+{% endif %}
+{% if tls.certificate is vyos_defined %}
cert /run/openvpn/{{ ifname }}_cert.pem
-{% endif %}
-{% if tls.private_key is vyos_defined %}
+{% endif %}
+{% if tls.private_key is vyos_defined %}
key /run/openvpn/{{ ifname }}_cert.key
-{% endif %}
-{% if tls.crypt_key is vyos_defined %}
+{% endif %}
+{% if tls.crypt_key is vyos_defined %}
tls-crypt /run/openvpn/{{ ifname }}_crypt.key
-{% endif %}
-{% if tls.crl is vyos_defined %}
+{% endif %}
+{% if tls.crl is vyos_defined %}
crl-verify /run/openvpn/{{ ifname }}_crl.pem
-{% endif %}
-{% if tls.tls_version_min is vyos_defined %}
+{% endif %}
+{% if tls.tls_version_min is vyos_defined %}
tls-version-min {{ tls.tls_version_min }}
-{% endif %}
-{% if tls.dh_params is vyos_defined %}
+{% endif %}
+{% if tls.dh_params is vyos_defined %}
dh /run/openvpn/{{ ifname }}_dh.pem
-{% elif mode is vyos_defined('server') and tls.private_key is vyos_defined %}
+{% elif mode is vyos_defined('server') and tls.private_key is vyos_defined %}
dh none
-{% endif %}
-{% if tls.auth_key is vyos_defined %}
-{% if mode == 'client' %}
+{% endif %}
+{% if tls.auth_key is vyos_defined %}
+{% if mode == 'client' %}
tls-auth /run/openvpn/{{ ifname }}_auth.key 1
-{% elif mode == 'server' %}
+{% elif mode == 'server' %}
tls-auth /run/openvpn/{{ ifname }}_auth.key 0
+{% endif %}
{% endif %}
-{% endif %}
-{% if tls.role is vyos_defined('active') %}
+{% if tls.role is vyos_defined('active') %}
tls-client
-{% elif tls.role is vyos_defined('passive') %}
+{% elif tls.role is vyos_defined('passive') %}
tls-server
-{% endif %}
+{% endif %}
{% endif %}
# Encryption options
{% if encryption is vyos_defined %}
-{% if encryption.cipher is vyos_defined %}
+{% if encryption.cipher is vyos_defined %}
cipher {{ encryption.cipher | openvpn_cipher }}
-{% if encryption.cipher is vyos_defined('bf128') %}
+{% if encryption.cipher is vyos_defined('bf128') %}
keysize 128
-{% elif encryption.cipher is vyos_defined('bf256') %}
+{% elif encryption.cipher is vyos_defined('bf256') %}
keysize 256
+{% endif %}
{% endif %}
-{% endif %}
-{% if encryption.ncp_ciphers is vyos_defined %}
+{% if encryption.ncp_ciphers is vyos_defined %}
data-ciphers {{ encryption.ncp_ciphers | openvpn_ncp_ciphers }}
-{% endif %}
+{% endif %}
{% endif %}
{% if hash is vyos_defined %}
diff --git a/data/templates/openvpn/service-override.conf.j2 b/data/templates/openvpn/service-override.conf.j2
new file mode 100644
index 000000000..616ba3bfc
--- /dev/null
+++ b/data/templates/openvpn/service-override.conf.j2
@@ -0,0 +1,21 @@
+{% set options = namespace(value='') %}
+{% if openvpn_option is vyos_defined %}
+{% for option in openvpn_option %}
+{# Remove the '--' prefix from variable if it is presented #}
+{% if option.startswith('--') %}
+{% set option = option.split('--', maxsplit=1)[1] %}
+{% endif %}
+{# Workaround to pass '--push' options properly. Previously openvpn accepted this option without values in double-quotes #}
+{# But now it stopped doing this, so we need to add them for compatibility #}
+{# HOWEVER! This is a raw option and we do not promise that this or any other trick will work for all the cases. #}
+{# Using 'openvpn-option' you take all responsibility for compatibility for yourself. #}
+{% if option.startswith('push') and not (option.startswith('push "') and option.endswith('"')) %}
+{% set option = 'push \"%s\"' | format(option.split('push ', maxsplit=1)[1]) %}
+{% endif %}
+{% set options.value = options.value ~ ' --' ~ option %}
+{% endfor %}
+{% endif %}
+[Service]
+ExecStart=
+ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid {{ options.value }}
+
diff --git a/data/templates/openvpn/service-override.conf.tmpl b/data/templates/openvpn/service-override.conf.tmpl
deleted file mode 100644
index cba652223..000000000
--- a/data/templates/openvpn/service-override.conf.tmpl
+++ /dev/null
@@ -1,20 +0,0 @@
-[Service]
-ExecStart=
-ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid
-{%- if openvpn_option is vyos_defined %}
-{% for option in openvpn_option %}
-{# Remove the '--' prefix from variable if it is presented #}
-{% if option.startswith('--') %}
-{% set option = option.split('--', maxsplit=1)[1] %}
-{% endif %}
-{# Workaround to pass '--push' options properly. Previously openvpn accepted this option without values in double-quotes #}
-{# But now it stopped doing this, so we need to add them for compatibility #}
-{# HOWEVER! This is a raw option and we do not promise that this or any other trick will work for all the cases. #}
-{# Using 'openvpn-option' you take all responsibility for compatibility for yourself. #}
-{% if option.startswith('push') and not (option.startswith('push "') and option.endswith('"')) %}
-{% set option = 'push \"%s\"'|format(option.split('push ', maxsplit=1)[1]) %}
-{% endif %}
- --{{ option }}
-{%- endfor %}
-{% endif %}
-
diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.j2
index 99749b57a..f4001db64 100644
--- a/data/templates/salt-minion/minion.tmpl
+++ b/data/templates/salt-minion/minion.j2
@@ -32,17 +32,17 @@ log_file: /var/log/salt/minion
# ['garbage', 'trace', 'debug']
#
# Default: 'warning'
-log_level: {{ log_level }}
+log_level: warning
# Set the location of the salt master server, if the master server cannot be
# resolved, then the minion will fail to start.
master:
{% for host in master %}
-- {{ host }}
+ - {{ host | bracketize_ipv6 }}
{% endfor %}
# The user to run salt
-user: {{ user }}
+user: minion
# The directory to store the pki information in
pki_dir: /config/salt/pki/minion
@@ -52,10 +52,16 @@ pki_dir: /config/salt/pki/minion
# Since salt uses detached ids it is possible to run multiple minions on the
# same machine but with different ids, this can be useful for salt compute
# clusters.
-id: {{ salt_id }}
-
+id: {{ id }}
# The number of minutes between mine updates.
mine_interval: {{ interval }}
-verify_master_pubkey_sign: {{ verify_master_pubkey_sign }}
+{% if source_interface is vyos_defined %}
+# The name of the interface to use when establishing the connection to the Master.
+source_interface_name: {{ source_interface }}
+{% endif %}
+
+# Enables verification of the master-public-signature returned by the master
+# in auth-replies.
+verify_master_pubkey_sign: {{ 'True' if master_key is vyos_defined else 'False' }}
diff --git a/data/templates/vyos-hostsd/hosts.tmpl b/data/templates/vyos-hostsd/hosts.j2
index bc75d384e..5cad983b4 100644
--- a/data/templates/vyos-hostsd/hosts.tmpl
+++ b/data/templates/vyos-hostsd/hosts.j2
@@ -1,3 +1,4 @@
+{# j2lint: disable=single-statement-per-line #}
### Autogenerated by VyOS ###
### Do not edit, your changes will get overwritten ###
@@ -14,12 +15,12 @@ ff02::2 ip6-allrouters
{% if hosts is vyos_defined %}
# From 'system static-host-mapping' and DHCP server
-{% for tag, taghosts in hosts.items() %}
+{% for tag, taghosts in hosts.items() %}
# {{ tag }}
-{% for host, hostprops in taghosts.items() if hostprops.address is vyos_defined %}
-{% for addr in hostprops.address %}
-{{ "%-15s" | format(addr) }} {{ host }} {{ hostprops.aliases|join(' ') if hostprops.aliases is vyos_defined }}
-{% endfor %}
+{% for host, hostprops in taghosts.items() if hostprops.address is vyos_defined %}
+{% for addr in hostprops.address %}
+{{ "%-15s" | format(addr) }} {{ host }} {{ hostprops.aliases | join(' ') if hostprops.aliases is vyos_defined }}
+{% endfor %}
+{% endfor %}
{% endfor %}
-{% endfor %}
{% endif %}
diff --git a/data/templates/vyos-hostsd/resolv.conf.tmpl b/data/templates/vyos-hostsd/resolv.conf.j2
index 58a5f9312..5f651f1a1 100644
--- a/data/templates/vyos-hostsd/resolv.conf.tmpl
+++ b/data/templates/vyos-hostsd/resolv.conf.j2
@@ -5,12 +5,12 @@
{# the order of tags, then by the order of nameservers within that tag #}
{% for tag in name_server_tags_system %}
-{% if tag in name_servers %}
+{% if tag in name_servers %}
# {{ tag }}
-{% for ns in name_servers[tag] %}
+{% for ns in name_servers[tag] %}
nameserver {{ ns }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
{% endfor %}
{% if domain_name %}
@@ -18,8 +18,8 @@ domain {{ domain_name }}
{% endif %}
{% for tag in name_server_tags_system %}
-{% if tag in search_domains %}
+{% if tag in search_domains %}
# {{ tag }}
-search {{ search_domains[tag]|join(' ') }}
-{% endif %}
+search {{ search_domains[tag] | join(' ') }}
+{% endif %}
{% endfor %}
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index 4ea2d471d..2c4c1709d 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -374,6 +374,18 @@
<leafNode name="tftp-server-name">
<properties>
<help>TFTP server name</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>TFTP server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>TFTP server FQDN</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="time-offset">
@@ -402,6 +414,32 @@
<multi/>
</properties>
</leafNode>
+ <node name="vendor-option">
+ <properties>
+ <help>Vendor Specific Options</help>
+ </properties>
+ <children>
+ <node name="ubiquiti">
+ <properties>
+ <help>Ubiquiti specific parameters</help>
+ </properties>
+ <children>
+ <leafNode name="unifi-controller">
+ <properties>
+ <help>Address of UniFi controller</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of UniFi controller</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="wins-server">
<properties>
<help>IP address for Windows Internet Name Service (WINS) server</help>
diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in
index fb96571f5..10335b07e 100644
--- a/interface-definitions/dhcpv6-server.xml.in
+++ b/interface-definitions/dhcpv6-server.xml.in
@@ -338,6 +338,33 @@
</leafNode>
</children>
</tagNode>
+ <node name="vendor-option">
+ <properties>
+ <help>Vendor Specific Options</help>
+ </properties>
+ <children>
+ <node name="cisco">
+ <properties>
+ <help>Cisco specific parameters</help>
+ </properties>
+ <children>
+ <leafNode name="tftp-server">
+ <properties>
+ <help>TFTP server name</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>TFTP server IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i
index f3fc4444c..a56745380 100644
--- a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i
+++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i
@@ -69,7 +69,7 @@
#include <include/bgp/afi-allowas-in.xml.i>
<leafNode name="as-override">
<properties>
- <help>AS for routes sent to this peer to be the local AS</help>
+ <help>Override ASN in outbound updates to configured neighbor local-as</help>
<valueless/>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/default-route-distance.xml.i b/interface-definitions/include/interface/default-route-distance.xml.i
new file mode 100644
index 000000000..6eda52c91
--- /dev/null
+++ b/interface-definitions/include/interface/default-route-distance.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/default-route-distance.xml.i -->
+<leafNode name="default-route-distance">
+ <properties>
+ <help>Distance for installed default route</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Distance for the default route from DHCP server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>210</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i
index 098d02919..914b60503 100644
--- a/interface-definitions/include/interface/dhcp-options.xml.i
+++ b/interface-definitions/include/interface/dhcp-options.xml.i
@@ -19,25 +19,8 @@
<help>Identify the vendor client type to the DHCP server</help>
</properties>
</leafNode>
- <leafNode name="no-default-route">
- <properties>
- <help>Do not request routers from DHCP server</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="default-route-distance">
- <properties>
- <help>Distance for the default route from DHCP server</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Distance for the default route from DHCP server</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- <defaultValue>210</defaultValue>
- </leafNode>
+ #include <include/interface/no-default-route.xml.i>
+ #include <include/interface/default-route-distance.xml.i>
<leafNode name="reject">
<properties>
<help>IP addresses or subnets from which to reject DHCP leases</help>
diff --git a/interface-definitions/include/interface/no-default-route.xml.i b/interface-definitions/include/interface/no-default-route.xml.i
new file mode 100644
index 000000000..307fcff1e
--- /dev/null
+++ b/interface-definitions/include/interface/no-default-route.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/dhcp-options.xml.i -->
+<leafNode name="no-default-route">
+ <properties>
+ <help>Do not install default route to system</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/parameters-df.xml.i b/interface-definitions/include/interface/parameters-df.xml.i
new file mode 100644
index 000000000..82436b5e4
--- /dev/null
+++ b/interface-definitions/include/interface/parameters-df.xml.i
@@ -0,0 +1,26 @@
+<!-- include start from interface/parameters-df.xml.i -->
+<leafNode name="df">
+ <properties>
+ <help>Usage of the DF (don't Fragment) bit in outgoing packets</help>
+ <completionHelp>
+ <list>set unset inherit</list>
+ </completionHelp>
+ <valueHelp>
+ <format>set</format>
+ <description>Always set DF (don't fragment) bit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>unset</format>
+ <description>Always unset DF (don't fragment) bit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>inherit</format>
+ <description>Copy from the original IP header</description>
+ </valueHelp>
+ <constraint>
+ <regex>(set|unset|inherit)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>unset</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/parameters-dont-fragment.xml.i b/interface-definitions/include/interface/parameters-dont-fragment.xml.i
deleted file mode 100644
index d34f0a97b..000000000
--- a/interface-definitions/include/interface/parameters-dont-fragment.xml.i
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- include start from interface/parameters-df.xml.i -->
-<leafNode name="dont-fragment">
- <properties>
- <help>Specifies the usage of the dont fragment (DF) bit</help>
- <valueless/>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i
index b97971531..0a209bc3a 100644
--- a/interface-definitions/include/version/interfaces-version.xml.i
+++ b/interface-definitions/include/version/interfaces-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/interfaces-version.xml.i -->
-<syntaxVersion component='interfaces' version='25'></syntaxVersion>
+<syntaxVersion component='interfaces' version='26'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index fa5a78be5..9143ba6be 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -35,7 +35,7 @@
<help>IPv4 specific tunnel parameters</help>
</properties>
<children>
- #include <include/interface/parameters-dont-fragment.xml.i>
+ #include <include/interface/parameters-df.xml.i>
#include <include/interface/parameters-tos.xml.i>
#include <include/interface/parameters-ttl.xml.i>
</children>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 3a0b7a40c..8cd8f8c86 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -21,31 +21,8 @@
#include <include/interface/dial-on-demand.xml.i>
#include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
- <leafNode name="default-route">
- <properties>
- <help>Default route insertion behaviour</help>
- <completionHelp>
- <list>auto none force</list>
- </completionHelp>
- <constraint>
- <regex>^(auto|none|force)$</regex>
- </constraint>
- <constraintErrorMessage>PPPoE default-route option must be 'auto', 'none', or 'force'</constraintErrorMessage>
- <valueHelp>
- <format>auto</format>
- <description>Automatically install a default route</description>
- </valueHelp>
- <valueHelp>
- <format>none</format>
- <description>Do not install a default route</description>
- </valueHelp>
- <valueHelp>
- <format>force</format>
- <description>Replace existing default route</description>
- </valueHelp>
- </properties>
- <defaultValue>auto</defaultValue>
- </leafNode>
+ #include <include/interface/no-default-route.xml.i>
+ #include <include/interface/default-route-distance.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 9747b1816..8b50fe1b7 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -69,7 +69,7 @@
<help>IPv4 specific tunnel parameters</help>
</properties>
<children>
- #include <include/interface/parameters-dont-fragment.xml.i>
+ #include <include/interface/parameters-df.xml.i>
#include <include/interface/parameters-tos.xml.i>
#include <include/interface/parameters-ttl.xml.i>
<leafNode name="ttl">
diff --git a/interface-definitions/salt-minion.xml.in b/interface-definitions/salt-minion.xml.in
index d3b022d12..c3219cff3 100644
--- a/interface-definitions/salt-minion.xml.in
+++ b/interface-definitions/salt-minion.xml.in
@@ -15,20 +15,25 @@
<list>md5 sha1 sha224 sha256 sha384 sha512</list>
</completionHelp>
<constraint>
- <regex>^(md5|sha1|sha224|sha256|sha384|sha512)$</regex>
+ <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex>
</constraint>
</properties>
+ <defaultValue>sha256</defaultValue>
</leafNode>
<leafNode name="master">
<properties>
- <help>The hostname or IP address of the master.</help>
+ <help>Hostname or IP address of the Salt master server</help>
<valueHelp>
<format>ipv4</format>
- <description>Remote syslog server IPv4 address</description>
+ <description>Salt server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Salt server IPv6 address</description>
</valueHelp>
<valueHelp>
<format>hostname</format>
- <description>Remote syslog server FQDN</description>
+ <description>Salt server FQDN address</description>
</valueHelp>
<constraint>
<validator name="ip-address"/>
@@ -54,12 +59,14 @@
<validator name="numeric" argument="--range 1-1440"/>
</constraint>
</properties>
+ <defaultValue>60</defaultValue>
</leafNode>
<leafNode name="master-key">
<properties>
<help>URL with signature of master for auth reply verification</help>
</properties>
</leafNode>
+ #include <include/source-interface.xml.i>
</children>
</node>
</children>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index cbdf76fc3..6f82ce611 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -6,13 +6,96 @@
<properties>
<help>Monitor last lines of messages file</help>
</properties>
- <command>tail --follow=name /var/log/messages</command>
+ <command>journalctl --no-hostname --follow --boot</command>
<children>
<node name="colored">
<properties>
<help>Output log in a colored fashion</help>
</properties>
- <command>grc tail --follow=name /var/log/messages</command>
+ <command>grc journalctl --no-hostname --follow --boot</command>
+ </node>
+ <node name="dhcp">
+ <properties>
+ <help>Show log for Dynamic Host Control Protocol (DHCP)</help>
+ </properties>
+ <children>
+ <node name="server">
+ <properties>
+ <help>Show log for DHCP server</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server.service</command>
+ </node>
+ <node name="client">
+ <properties>
+ <help>Show DHCP client logs</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "dhclient@*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show DHCP client log on specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "dhclient@$6.service"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="dhcpv6">
+ <properties>
+ <help>Show log for Dynamic Host Control Protocol IPv6 (DHCPv6)</help>
+ </properties>
+ <children>
+ <node name="server">
+ <properties>
+ <help>Show log for DHCPv6 server</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server6.service</command>
+ </node>
+ <node name="client">
+ <properties>
+ <help>Show DHCPv6 client logs</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show DHCPv6 client log on specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@$6.service"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <leafNode name="kernel">
+ <properties>
+ <help>Monitor last lines of Linux Kernel log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --dmesg</command>
+ </leafNode>
+ <node name="pppoe">
+ <properties>
+ <help>Monitor last lines of PPPoE log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@pppoe*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Monitor last lines of PPPoE log for specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --follow --unit "ppp@$6.service"</command>
+ </tagNode>
+ </children>
</node>
<node name="protocol">
<properties>
@@ -23,67 +106,67 @@
<properties>
<help>Monitor log for OSPF</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/ospfd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospfd</command>
</leafNode>
<leafNode name="ospfv3">
<properties>
<help>Monitor log for OSPF for IPv6</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/ospf6d</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospf6d</command>
</leafNode>
<leafNode name="bgp">
<properties>
<help>Monitor log for BGP</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/bgpd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bgpd</command>
</leafNode>
<leafNode name="rip">
<properties>
<help>Monitor log for RIP</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/ripd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripd</command>
</leafNode>
<leafNode name="ripng">
<properties>
<help>Monitor log for RIPng</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/ripngd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripngd</command>
</leafNode>
<leafNode name="static">
<properties>
<help>Monitor log for static route</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/staticd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/staticd</command>
</leafNode>
<leafNode name="multicast">
<properties>
<help>Monitor log for Multicast protocol</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/pimd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/pimd</command>
</leafNode>
<leafNode name="isis">
<properties>
<help>Monitor log for ISIS</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/isisd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/isisd</command>
</leafNode>
<leafNode name="nhrp">
<properties>
<help>Monitor log for NHRP</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/nhrpd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/nhrpd</command>
</leafNode>
<leafNode name="bfd">
<properties>
<help>Monitor log for BFD</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/bfdd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bfdd</command>
</leafNode>
<leafNode name="mpls">
<properties>
<help>Monitor log for MPLS</help>
</properties>
- <command>journalctl --follow --boot /usr/lib/frr/ldpd</command>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ldpd</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 15bbc7f42..954369712 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -179,9 +179,9 @@
</tagNode>
<leafNode name="kernel">
<properties>
- <help>Show messages in kernel ring buffer</help>
+ <help>Show log for Linux Kernel</help>
</properties>
- <command>sudo dmesg</command>
+ <command>journalctl --no-hostname --boot --dmesg</command>
</leafNode>
<leafNode name="lldp">
<properties>
@@ -212,6 +212,23 @@
</tagNode>
</children>
</node>
+ <node name="pppoe">
+ <properties>
+ <help>Show log for PPPoE</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@pppoe*.service"</command>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Show PPPoE log on specific interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "ppp@$6.service"</command>
+ </tagNode>
+ </children>
+ </node>
<node name="protocol">
<properties>
<help>Show log for Routing Protocols</help>
diff --git a/python/vyos/base.py b/python/vyos/base.py
index fd22eaccd..78067d5b2 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2022 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
@@ -15,6 +15,12 @@
from textwrap import fill
+class Warning():
+ def __init__(self, message):
+ # Reformat the message and trim it to 72 characters in length
+ message = fill(message, width=72)
+ print(f'\nWARNING: {message}')
+
class DeprecationWarning():
def __init__(self, message):
# Reformat the message and trim it to 72 characters in length
diff --git a/python/vyos/config.py b/python/vyos/config.py
index 858c7bdd7..287fd2ed1 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -156,26 +156,28 @@ class Config(object):
"""
if self._session_config is None:
return False
+
+ # Assume the path is a node path first
if self._session_config.exists(self._make_path(path)):
return True
- # libvyosconfig exists() works only for _nodes_, not _values_
- # libvyattacfg also worked for values, so we emulate that case here
- if isinstance(path, str):
- path = re.split(r'\s+', path)
- path_without_value = path[:-1]
- path_str = " ".join(path_without_value)
- try:
- value = self._session_config.return_value(self._make_path(path_str))
- values = self._session_config.return_values(self._make_path(path_str))
- except vyos.configtree.ConfigTreeError:
- # node/value doesn't exist
- return False
- if value and path[-1] == value:
- return True
- if isinstance(values, list) and path[-1] in values:
- return True
+ else:
+ # If that check fails, it may mean the path has a value at the end.
+ # libvyosconfig exists() works only for _nodes_, not _values_
+ # libvyattacfg also worked for values, so we emulate that case here
+ if isinstance(path, str):
+ path = re.split(r'\s+', path)
+ path_without_value = path[:-1]
+ try:
+ # return_values() is safe to use with single-value nodes,
+ # it simply returns a single-item list in that case.
+ values = self._session_config.return_values(self._make_path(path_without_value))
- return False
+ # If we got this far, the node does exist and has values,
+ # so we need to check if it has the value in question among its values.
+ return (path[-1] in values)
+ except vyos.configtree.ConfigTreeError:
+ # Even the parent node doesn't exist at all
+ return False
def session_changed(self):
"""
@@ -402,26 +404,29 @@ class Config(object):
"""
if self._running_config is None:
return False
+
+ # Assume the path is a node path first
if self._running_config.exists(self._make_path(path)):
return True
- # libvyosconfig exists() works only for _nodes_, not _values_
- # libvyattacfg also worked for values, so we emulate that case here
- if isinstance(path, str):
- path = re.split(r'\s+', path)
- path_without_value = path[:-1]
- path_str = " ".join(path_without_value)
- try:
- value = self._running_config.return_value(self._make_path(path_str))
- values = self._running_config.return_values(self._make_path(path_str))
- except vyos.configtree.ConfigTreeError:
- # node/value doesn't exist
- return False
- if value and path[-1] == value:
- return True
- if isinstance(values, list) and path[-1] in values:
- return True
+ else:
+ # If that check fails, it may mean the path has a value at the end.
+ # libvyosconfig exists() works only for _nodes_, not _values_
+ # libvyattacfg also worked for values, so we emulate that case here
+ if isinstance(path, str):
+ path = re.split(r'\s+', path)
+ path_without_value = path[:-1]
+ try:
+ # return_values() is safe to use with single-value nodes,
+ # it simply returns a single-item list in that case.
+ values = self._running_config.return_values(self._make_path(path_without_value))
+
+ # If we got this far, the node does exist and has values,
+ # so we need to check if it has the value in question among its values.
+ return (path[-1] in values)
+ except vyos.configtree.ConfigTreeError:
+ # Even the parent node doesn't exist at all
+ return False
- return False
def return_effective_value(self, path, default=None):
"""
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 551c27b67..04ddc10e9 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2022 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
@@ -104,6 +104,11 @@ def list_diff(first, second):
second = set(second)
return [item for item in first if item not in second]
+def is_node_changed(conf, path):
+ from vyos.configdiff import get_config_diff
+ D = get_config_diff(conf, key_mangling=('-', '_'))
+ return D.is_node_changed(path)
+
def leaf_node_changed(conf, path):
"""
Check if a leaf node was altered. If it has been altered - values has been
@@ -114,7 +119,6 @@ def leaf_node_changed(conf, path):
"""
from vyos.configdiff import get_config_diff
D = get_config_diff(conf, key_mangling=('-', '_'))
- D.set_level(conf.get_level())
(new, old) = D.get_value_diff(path)
if new != old:
if isinstance(old, dict):
@@ -144,12 +148,11 @@ def node_changed(conf, path, key_mangling=None, recursive=False):
"""
from vyos.configdiff import get_config_diff, Diff
D = get_config_diff(conf, key_mangling)
- D.set_level(conf.get_level())
# get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE, recursive=recursive)['delete'].keys()
return list(keys)
-def get_removed_vlans(conf, dict):
+def get_removed_vlans(conf, path, dict):
"""
Common function to parse a dictionary retrieved via get_config_dict() and
determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q.
@@ -159,16 +162,17 @@ def get_removed_vlans(conf, dict):
# Check vif, vif-s/vif-c VLAN interfaces for removal
D = get_config_diff(conf, key_mangling=('-', '_'))
D.set_level(conf.get_level())
+
# get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
- keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys()
+ keys = D.get_child_nodes_diff(path + ['vif'], expand_nodes=Diff.DELETE)['delete'].keys()
if keys: dict['vif_remove'] = [*keys]
# get_child_nodes() will return dict_keys(), mangle this into a list with PEP448
- keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys()
+ keys = D.get_child_nodes_diff(path + ['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys()
if keys: dict['vif_s_remove'] = [*keys]
for vif in dict.get('vif_s', {}).keys():
- keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys()
+ keys = D.get_child_nodes_diff(path + ['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys()
if keys: dict['vif_s'][vif]['vif_c_remove'] = [*keys]
return dict
@@ -212,10 +216,6 @@ def is_member(conf, interface, intftype=None):
intftype = intftypes if intftype == None else [intftype]
- # set config level to root
- old_level = conf.get_level()
- conf.set_level([])
-
for iftype in intftype:
base = ['interfaces', iftype]
for intf in conf.list_nodes(base):
@@ -225,7 +225,6 @@ def is_member(conf, interface, intftype=None):
get_first_key=True, no_tag_node_value_mangle=True)
ret_val.update({intf : tmp})
- old_level = conf.set_level(old_level)
return ret_val
def is_mirror_intf(conf, interface, direction=None):
@@ -247,8 +246,6 @@ def is_mirror_intf(conf, interface, direction=None):
direction = directions if direction == None else [direction]
ret_val = None
- old_level = conf.get_level()
- conf.set_level([])
base = ['interfaces']
for dir in direction:
@@ -262,7 +259,6 @@ def is_mirror_intf(conf, interface, direction=None):
get_first_key=True)
ret_val = {intf : tmp}
- old_level = conf.set_level(old_level)
return ret_val
def has_vlan_subinterface_configured(conf, intf):
@@ -276,15 +272,11 @@ def has_vlan_subinterface_configured(conf, intf):
from vyos.ifconfig import Section
ret = False
- old_level = conf.get_level()
- conf.set_level([])
-
intfpath = ['interfaces', Section.section(intf), intf]
if ( conf.exists(intfpath + ['vif']) or
conf.exists(intfpath + ['vif-s'])):
ret = True
- conf.set_level(old_level)
return ret
def is_source_interface(conf, interface, intftype=None):
@@ -306,11 +298,6 @@ def is_source_interface(conf, interface, intftype=None):
'have a source-interface')
intftype = intftypes if intftype == None else [intftype]
-
- # set config level to root
- old_level = conf.get_level()
- conf.set_level([])
-
for it in intftype:
base = ['interfaces', it]
for intf in conf.list_nodes(base):
@@ -319,7 +306,6 @@ def is_source_interface(conf, interface, intftype=None):
ret_val = intf
break
- old_level = conf.set_level(old_level)
return ret_val
def get_dhcp_interfaces(conf, vrf=None):
@@ -330,40 +316,67 @@ def get_dhcp_interfaces(conf, vrf=None):
if not dict:
return dhcp_interfaces
- def check_dhcp(config, ifname):
+ def check_dhcp(config):
+ ifname = config['ifname']
tmp = {}
if 'address' in config and 'dhcp' in config['address']:
options = {}
- if 'dhcp_options' in config and 'default_route_distance' in config['dhcp_options']:
- options.update({'distance' : config['dhcp_options']['default_route_distance']})
+ if dict_search('dhcp_options.default_route_distance', config) != None:
+ options.update({'dhcp_options' : config['dhcp_options']})
if 'vrf' in config:
if vrf is config['vrf']: tmp.update({ifname : options})
else: tmp.update({ifname : options})
+
return tmp
for section, interface in dict.items():
for ifname in interface:
+ # always reset config level, as get_interface_dict() will alter it
+ conf.set_level([])
# we already have a dict representation of the config from get_config_dict(),
# but with the extended information from get_interface_dict() we also
# get the DHCP client default-route-distance default option if not specified.
- ifconfig = get_interface_dict(conf, ['interfaces', section], ifname)
+ _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname)
- tmp = check_dhcp(ifconfig, ifname)
+ tmp = check_dhcp(ifconfig)
dhcp_interfaces.update(tmp)
# check per VLAN interfaces
for vif, vif_config in ifconfig.get('vif', {}).items():
- tmp = check_dhcp(vif_config, f'{ifname}.{vif}')
+ tmp = check_dhcp(vif_config)
dhcp_interfaces.update(tmp)
# check QinQ VLAN interfaces
- for vif_s, vif_s_config in ifconfig.get('vif-s', {}).items():
- tmp = check_dhcp(vif_s_config, f'{ifname}.{vif_s}')
+ for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items():
+ tmp = check_dhcp(vif_s_config)
dhcp_interfaces.update(tmp)
- for vif_c, vif_c_config in vif_s_config.get('vif-c', {}).items():
- tmp = check_dhcp(vif_c_config, f'{ifname}.{vif_s}.{vif_c}')
+ for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ tmp = check_dhcp(vif_c_config)
dhcp_interfaces.update(tmp)
return dhcp_interfaces
+def get_pppoe_interfaces(conf, vrf=None):
+ """ Common helper functions to retrieve all interfaces from current CLI
+ sessions that have DHCP configured. """
+ pppoe_interfaces = {}
+ for ifname in conf.list_nodes(['interfaces', 'pppoe']):
+ # always reset config level, as get_interface_dict() will alter it
+ conf.set_level([])
+ # we already have a dict representation of the config from get_config_dict(),
+ # but with the extended information from get_interface_dict() we also
+ # get the DHCP client default-route-distance default option if not specified.
+ ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
+
+ options = {}
+ if 'default_route_distance' in ifconfig:
+ options.update({'default_route_distance' : ifconfig['default_route_distance']})
+ if 'no_default_route' in ifconfig:
+ options.update({'no_default_route' : {}})
+ if 'vrf' in ifconfig:
+ if vrf is ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
+ else: pppoe_interfaces.update({ifname : options})
+
+ return pppoe_interfaces
+
def get_interface_dict(config, base, ifname=''):
"""
Common utility function to retrieve and mangle the interfaces configuration
@@ -373,7 +386,6 @@ def get_interface_dict(config, base, ifname=''):
Return a dictionary with the necessary interface config keys.
"""
-
if not ifname:
from vyos import ConfigError
# determine tagNode instance
@@ -390,9 +402,8 @@ def get_interface_dict(config, base, ifname=''):
for vif in ['vif', 'vif_s']:
if vif in default_values: del default_values[vif]
- # setup config level which is extracted in get_removed_vlans()
- config.set_level(base + [ifname])
- dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True,
+ dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'),
+ get_first_key=True,
no_tag_node_value_mangle=True)
# Check if interface has been removed. We must use exists() as
@@ -400,8 +411,8 @@ def get_interface_dict(config, base, ifname=''):
# node like the following exists.
# +macsec macsec1 {
# +}
- if not config.exists([]):
- dict.update({'deleted' : ''})
+ if not config.exists(base + [ifname]):
+ dict.update({'deleted' : {}})
# Add interface instance name into dictionary
dict.update({'ifname': ifname})
@@ -428,7 +439,7 @@ def get_interface_dict(config, base, ifname=''):
# XXX: T2665: blend in proper DHCPv6-PD default values
dict = T2665_set_dhcpv6pd_defaults(dict)
- address = leaf_node_changed(config, ['address'])
+ address = leaf_node_changed(config, base + [ifname, 'address'])
if address: dict.update({'address_old' : address})
# Check if we are a member of a bridge device
@@ -459,10 +470,10 @@ def get_interface_dict(config, base, ifname=''):
tmp = is_member(config, dict['source_interface'], 'bonding')
if tmp: dict.update({'source_interface_is_bond_member' : tmp})
- mac = leaf_node_changed(config, ['mac'])
+ mac = leaf_node_changed(config, base + [ifname, 'mac'])
if mac: dict.update({'mac_old' : mac})
- eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64'])
+ eui64 = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'eui64'])
if eui64:
tmp = dict_search('ipv6.address', dict)
if not tmp:
@@ -474,6 +485,9 @@ def get_interface_dict(config, base, ifname=''):
# identical for all types of VLAN interfaces as they all include the same
# XML definitions which hold the defaults.
for vif, vif_config in dict.get('vif', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
+
default_vif_values = defaults(base + ['vif'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
# remove the default values from the dict.
@@ -483,7 +497,7 @@ def get_interface_dict(config, base, ifname=''):
# Only add defaults if interface is not about to be deleted - this is
# to keep a cleaner config dict.
if 'deleted' not in dict:
- address = leaf_node_changed(config, ['vif', vif, 'address'])
+ address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address'])
if address: dict['vif'][vif].update({'address_old' : address})
dict['vif'][vif] = dict_merge(default_vif_values, dict['vif'][vif])
@@ -505,6 +519,9 @@ def get_interface_dict(config, base, ifname=''):
if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''})
for vif_s, vif_s_config in dict.get('vif_s', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'})
+
default_vif_s_values = defaults(base + ['vif-s'])
# XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c
if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c']
@@ -517,7 +534,7 @@ def get_interface_dict(config, base, ifname=''):
# Only add defaults if interface is not about to be deleted - this is
# to keep a cleaner config dict.
if 'deleted' not in dict:
- address = leaf_node_changed(config, ['vif-s', vif_s, 'address'])
+ address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address'])
if address: dict['vif_s'][vif_s].update({'address_old' : address})
dict['vif_s'][vif_s] = dict_merge(default_vif_s_values,
@@ -541,6 +558,9 @@ def get_interface_dict(config, base, ifname=''):
if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''})
for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'})
+
default_vif_c_values = defaults(base + ['vif-s', 'vif-c'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
@@ -551,7 +571,7 @@ def get_interface_dict(config, base, ifname=''):
# Only add defaults if interface is not about to be deleted - this is
# to keep a cleaner config dict.
if 'deleted' not in dict:
- address = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'address'])
+ address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address'])
if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update(
{'address_old' : address})
@@ -578,8 +598,8 @@ def get_interface_dict(config, base, ifname=''):
if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''})
# Check vif, vif-s/vif-c VLAN interfaces for removal
- dict = get_removed_vlans(config, dict)
- return dict
+ dict = get_removed_vlans(config, base + [ifname], dict)
+ return ifname, dict
def get_vlan_ids(interface):
"""
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 1062d51ee..438485d98 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2022 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
@@ -205,10 +205,10 @@ def verify_mirror_redirect(config):
raise ConfigError(f'Requested redirect interface "{redirect_ifname}" '\
'does not exist!')
- if dict_search('traffic_policy.in', config) != None:
+ if ('mirror' in config or 'redirect' in config) and dict_search('traffic_policy.in', config) is not None:
# XXX: support combination of limiting and redirect/mirror - this is an
# artificial limitation
- raise ConfigError('Can not use ingress policy tigether with mirror or redirect!')
+ raise ConfigError('Can not use ingress policy together with mirror or redirect!')
def verify_authentication(config):
"""
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index 7cb3968df..276c34cd7 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -42,7 +42,7 @@ class GeneveIf(Interface):
# arguments used by iproute2. For more information please refer to:
# - https://man7.org/linux/man-pages/man8/ip-link.8.html
mapping = {
- 'parameters.ip.dont_fragment': 'df set',
+ 'parameters.ip.df' : 'df',
'parameters.ip.tos' : 'tos',
'parameters.ip.ttl' : 'ttl',
'parameters.ipv6.flowlabel' : 'flowlabel',
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 6b0f08fd4..22441d1d2 100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -736,8 +736,9 @@ class Interface(Control):
if all_rp_filter == 1: global_setting = 'strict'
elif all_rp_filter == 2: global_setting = 'loose'
- print(f'WARNING: Global source-validation is set to "{global_setting}\n"' \
- 'this overrides per interface setting!')
+ from vyos.base import Warning
+ Warning(f'Global source-validation is set to "{global_setting} '\
+ f'this overrides per interface setting!')
tmp = self.get_interface('rp_filter')
if int(tmp) == value:
@@ -1243,8 +1244,8 @@ class Interface(Control):
tmp = {'dhcp_options' : { 'host_name' : hostname}}
self._config = dict_merge(tmp, self._config)
- render(options_file, 'dhcp-client/daemon-options.tmpl', self._config)
- render(config_file, 'dhcp-client/ipv4.tmpl', self._config)
+ render(options_file, 'dhcp-client/daemon-options.j2', self._config)
+ render(config_file, 'dhcp-client/ipv4.j2', self._config)
# When the DHCP client is restarted a brief outage will occur, as
# the old lease is released a new one is acquired (T4203). We will
@@ -1274,8 +1275,7 @@ class Interface(Control):
systemd_service = f'dhcp6c@{ifname}.service'
if enable and 'disable' not in self._config:
- render(config_file, 'dhcp-client/ipv6.tmpl',
- self._config)
+ render(config_file, 'dhcp-client/ipv6.j2', self._config)
# We must ignore any return codes. This is required to enable
# DHCPv6-PD for interfaces which are yet not up and running.
@@ -1587,12 +1587,10 @@ class Interface(Control):
tmp['source_interface'] = ifname
tmp['vlan_id'] = vif_s_id
- vif_s_ifname = f'{ifname}.{vif_s_id}'
- vif_s_config['ifname'] = vif_s_ifname
-
# It is not possible to change the VLAN encapsulation protocol
# "on-the-fly". For this "quirk" we need to actively delete and
# re-create the VIF-S interface.
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
if self.exists(vif_s_ifname):
cur_cfg = get_interface_config(vif_s_ifname)
protocol = dict_search('linkinfo.info_data.protocol', cur_cfg).lower()
@@ -1614,7 +1612,6 @@ class Interface(Control):
tmp['vlan_id'] = vif_c_id
vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
- vif_c_config['ifname'] = vif_c_ifname
c_vlan = VLANIf(vif_c_ifname, **tmp)
c_vlan.update(vif_c_config)
@@ -1625,10 +1622,7 @@ class Interface(Control):
# create/update 802.1q VLAN interfaces
for vif_id, vif_config in config.get('vif', {}).items():
-
vif_ifname = f'{ifname}.{vif_id}'
- vif_config['ifname'] = vif_ifname
-
tmp = deepcopy(VLANIf.get_config())
tmp['source_interface'] = ifname
tmp['vlan_id'] = vif_id
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
index 1d13264bf..63ffc8069 100644
--- a/python/vyos/ifconfig/pppoe.py
+++ b/python/vyos/ifconfig/pppoe.py
@@ -27,12 +27,13 @@ class PPPoEIf(Interface):
},
}
- def _remove_routes(self, vrf=''):
+ def _remove_routes(self, vrf=None):
# Always delete default routes when interface is removed
+ vrf_cmd = ''
if vrf:
- vrf = f'-c "vrf {vrf}"'
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"')
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no ipv6 route ::/0 {self.ifname} tag 210"')
+ vrf_cmd = f'-c "vrf {vrf}"'
+ self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"')
+ self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ipv6 route ::/0 {self.ifname} tag 210"')
def remove(self):
"""
@@ -44,11 +45,11 @@ class PPPoEIf(Interface):
>>> i = Interface('pppoe0')
>>> i.remove()
"""
-
+ vrf = None
tmp = get_interface_config(self.ifname)
- vrf = ''
if 'master' in tmp:
- self._remove_routes(tmp['master'])
+ vrf = tmp['master']
+ self._remove_routes(vrf)
# remove bond master which places members in disabled state
super().remove()
@@ -84,10 +85,12 @@ class PPPoEIf(Interface):
self._config = config
# remove old routes from an e.g. old VRF assignment
- vrf = ''
- if 'vrf_old' in config:
- vrf = config['vrf_old']
- self._remove_routes(vrf)
+ if 'shutdown_required':
+ vrf = None
+ tmp = get_interface_config(self.ifname)
+ if 'master' in tmp:
+ vrf = tmp['master']
+ self._remove_routes(vrf)
# DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do
# not require an 'address dhcpv6' CLI option as with other interfaces
@@ -98,54 +101,15 @@ class PPPoEIf(Interface):
super().update(config)
- if 'default_route' not in config or config['default_route'] == 'none':
- return
-
- #
- # Set default routes pointing to pppoe interface
- #
- vrf = ''
- sed_opt = '^ip route'
-
- install_v4 = True
- install_v6 = True
-
# generate proper configuration string when VRFs are in use
+ vrf = ''
if 'vrf' in config:
tmp = config['vrf']
vrf = f'-c "vrf {tmp}"'
- sed_opt = f'vrf {tmp}'
-
- if config['default_route'] == 'auto':
- # only add route if there is no default route present
- tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"')
- for line in tmp.splitlines():
- line = line.lstrip()
- if line.startswith('ip route 0.0.0.0/0'):
- install_v4 = False
- continue
-
- if 'ipv6' in config and line.startswith('ipv6 route ::/0'):
- install_v6 = False
- continue
-
- elif config['default_route'] == 'force':
- # Force means that all static routes are replaced with the ones from this interface
- tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"')
- for line in tmp.splitlines():
- if self.ifname in line:
- # It makes no sense to remove a route with our interface and the later re-add it.
- # This will only make traffic disappear - which is a no-no!
- continue
-
- line = line.lstrip()
- if line.startswith('ip route 0.0.0.0/0'):
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"')
-
- if 'ipv6' in config and line.startswith('ipv6 route ::/0'):
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"')
-
- if install_v4:
- self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210"')
- if install_v6 and 'ipv6' in config:
- self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210"')
+
+ if 'no_default_route' not in config:
+ # Set default route(s) pointing to PPPoE interface
+ distance = config['default_route_distance']
+ self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210 {distance}"')
+ if 'ipv6' in config:
+ self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210 {distance}"')
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 516a19f24..5baff10a9 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -57,7 +57,7 @@ class VXLANIf(Interface):
'group' : 'group',
'external' : 'external',
'gpe' : 'gpe',
- 'parameters.ip.dont_fragment': 'df set',
+ 'parameters.ip.df' : 'df',
'parameters.ip.tos' : 'tos',
'parameters.ip.ttl' : 'ttl',
'parameters.ipv6.flowlabel' : 'flowlabel',
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 493feed5b..3d62d269c 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -11,11 +11,35 @@ interfaces {
smp-affinity auto
speed auto
}
+ ethernet eth2 {
+ address 100.100.0.1/24
+ duplex auto
+ smp-affinity auto
+ speed auto
+ }
loopback lo {
}
}
protocols {
static {
+ arp 192.168.0.20 {
+ hwaddr 00:50:00:00:00:20
+ }
+ arp 192.168.0.30 {
+ hwaddr 00:50:00:00:00:30
+ }
+ arp 192.168.0.40 {
+ hwaddr 00:50:00:00:00:40
+ }
+ arp 100.100.0.2 {
+ hwaddr 00:50:00:00:02:02
+ }
+ arp 100.100.0.3 {
+ hwaddr 00:50:00:00:02:03
+ }
+ arp 100.100.0.4 {
+ hwaddr 00:50:00:00:02:04
+ }
route 0.0.0.0/0 {
next-hop 100.64.0.1 {
}
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index ba5acf5d6..816ba6dcd 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -78,18 +78,25 @@ class BasicInterfaceTest:
# choose IPv6 minimum MTU value for tests - this must always work
_mtu = '1280'
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
+ super(BasicInterfaceTest.TestCase, cls).setUpClass()
+
# Setup mirror interfaces for SPAN (Switch Port Analyzer)
- for span in self._mirror_interfaces:
+ for span in cls._mirror_interfaces:
section = Section.section(span)
- self.cli_set(['interfaces', section, span])
+ cls.cli_set(cls, ['interfaces', section, span])
- def tearDown(self):
+ @classmethod
+ def tearDownClass(cls):
# Tear down mirror interfaces for SPAN (Switch Port Analyzer)
- for span in self._mirror_interfaces:
+ for span in cls._mirror_interfaces:
section = Section.section(span)
- self.cli_delete(['interfaces', section, span])
+ cls.cli_delete(cls, ['interfaces', section, span])
+ super(BasicInterfaceTest.TestCase, cls).tearDownClass()
+
+ def tearDown(self):
self.cli_delete(self._base_path)
self.cli_commit()
@@ -232,6 +239,7 @@ class BasicInterfaceTest:
self.cli_commit()
for interface in self._interfaces:
+ self.assertIn(AF_INET6, ifaddresses(interface))
for addr in ifaddresses(interface)[AF_INET6]:
self.assertTrue(is_ipv6_link_local(addr['addr']))
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 5448295fa..37e6b9e7a 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -38,7 +38,7 @@ sysfs_config = {
class TestFirewall(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestFirewall, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -49,8 +49,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
-
- super(cls, cls).tearDownClass()
+ super(TestFirewall, cls).tearDownClass()
def tearDown(self):
self.cli_delete(['interfaces', 'ethernet', 'eth0', 'firewall'])
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 9bb561275..237abb487 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -55,7 +55,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(BondingInterfaceTest, cls).setUpClass()
def test_add_single_ip_address(self):
super().test_add_single_ip_address()
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index f2e111425..ca0ead9e8 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -56,7 +56,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(BridgeInterfaceTest, cls).setUpClass()
def tearDown(self):
for intf in self._interfaces:
diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py
index dedc6fe05..d96ec2c5d 100755
--- a/smoketest/scripts/cli/test_interfaces_dummy.py
+++ b/smoketest/scripts/cli/test_interfaces_dummy.py
@@ -24,7 +24,7 @@ class DummyInterfaceTest(BasicInterfaceTest.TestCase):
cls._base_path = ['interfaces', 'dummy']
cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089']
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(DummyInterfaceTest, cls).setUpClass()
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index ee7649af8..05d2ae5f5 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -18,13 +18,19 @@ import os
import re
import unittest
+from netifaces import AF_INET
+from netifaces import AF_INET6
+from netifaces import ifaddresses
+
from base_interfaces_test import BasicInterfaceTest
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.pki import CERT_BEGIN
+from vyos.template import is_ipv6
from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
+from vyos.validate import is_ipv6_link_local
server_ca_root_cert_data = """
MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw
@@ -128,7 +134,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
cls._macs[interface] = read_file(f'/sys/class/net/{interface}/address')
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(EthernetInterfaceTest, cls).setUpClass()
def tearDown(self):
for interface in self._interfaces:
@@ -140,13 +146,20 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(self._base_path + [interface, 'speed', 'auto'])
self.cli_set(self._base_path + [interface, 'hw-id', self._macs[interface]])
- # Tear down mirror interfaces for SPAN (Switch Port Analyzer)
- for span in self._mirror_interfaces:
- section = Section.section(span)
- self.cli_delete(['interfaces', section, span])
-
self.cli_commit()
+ # Verify that no address remains on the system as this is an eternal
+ # interface.
+ for intf in self._interfaces:
+ self.assertNotIn(AF_INET, ifaddresses(intf))
+ # required for IPv6 link-local address
+ self.assertIn(AF_INET6, ifaddresses(intf))
+ for addr in ifaddresses(intf)[AF_INET6]:
+ # checking link local addresses makes no sense
+ if is_ipv6_link_local(addr['addr']):
+ continue
+ self.assertFalse(is_intf_addr_assigned(intf, addr['addr']))
+
def test_offloading_rps(self):
# enable RPS on all available CPUs, RPS works woth a CPU bitmask,
# where each bit represents a CPU (core/thread). The formula below
diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py
index 6233ade6e..0e5098aa7 100755
--- a/smoketest/scripts/cli/test_interfaces_geneve.py
+++ b/smoketest/scripts/cli/test_interfaces_geneve.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -34,7 +34,7 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase):
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(GeneveInterfaceTest, cls).setUpClass()
def test_geneve_parameters(self):
tos = '40'
@@ -43,7 +43,7 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase):
for option in self._options.get(intf, []):
self.cli_set(self._base_path + [intf] + option.split())
- self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment'])
+ self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set'])
self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos])
self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)])
ttl += 10
diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
index 06ced5c40..aed8e6f15 100755
--- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py
+++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py
@@ -39,7 +39,7 @@ class L2TPv3InterfaceTest(BasicInterfaceTest.TestCase):
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(L2TPv3InterfaceTest, cls).setUpClass()
def test_add_single_ip_address(self):
super().test_add_single_ip_address()
diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py
index 85b5ca6d6..5ff9c250e 100755
--- a/smoketest/scripts/cli/test_interfaces_loopback.py
+++ b/smoketest/scripts/cli/test_interfaces_loopback.py
@@ -29,7 +29,7 @@ class LoopbackInterfaceTest(BasicInterfaceTest.TestCase):
cls._base_path = ['interfaces', 'loopback']
cls._interfaces = ['lo']
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(LoopbackInterfaceTest, cls).setUpClass()
def tearDown(self):
self.cli_delete(self._base_path)
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index 5b10bfa44..e5e5a558e 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -53,7 +53,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(MACsecInterfaceTest, cls).setUpClass()
def test_macsec_encryption(self):
# MACsec can be operating in authentication and encryption mode - both
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index f8a6ae986..b2143d16e 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -37,10 +37,46 @@ PROCESS_NAME = 'openvpn'
base_path = ['interfaces', 'openvpn']
-cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
-key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww'
-dh_data = 'MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFOFxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZUHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS67q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg=='
-ovpn_key_data = '443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa431d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e7042a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e65210b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a'
+cert_data = """
+MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw
+WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
+bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx
+MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV
+BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP
+UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3
+QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu
++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz
+ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93
++dm/LDnp7C0=
+"""
+
+key_data = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx
+2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7
+u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww
+"""
+
+dh_data = """
+MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFO
+FxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZ
+UHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+
+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS6
+7q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv
+108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg==
+"""
+
+ovpn_key_data = """
+443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa4
+31d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e704
+2a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043
+e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4
+adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c
+9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e6521
+0b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece
+4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a
+"""
remote_port = '1194'
protocol = 'udp'
@@ -59,20 +95,28 @@ def get_vrf(interface):
return tmp
class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
- self.cli_set(['vrf', 'name', vrf_name, 'table', '12345'])
+ @classmethod
+ def setUpClass(cls):
+ super(TestInterfacesOpenVPN, cls).setUpClass()
- self.cli_set(['pki', 'ca', 'ovpn_test', 'certificate', cert_data])
- self.cli_set(['pki', 'certificate', 'ovpn_test', 'certificate', cert_data])
- self.cli_set(['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data])
- self.cli_set(['pki', 'dh', 'ovpn_test', 'parameters', dh_data])
- self.cli_set(['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data])
+ cls.cli_set(cls, ['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
+ cls.cli_set(cls, ['vrf', 'name', vrf_name, 'table', '12345'])
+
+ cls.cli_set(cls, ['pki', 'ca', 'ovpn_test', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'dh', 'ovpn_test', 'parameters', dh_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data.replace('\n','')])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if])
+ cls.cli_delete(cls, ['vrf'])
+
+ super(TestInterfacesOpenVPN, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
- self.cli_delete(['interfaces', 'dummy', dummy_if])
- self.cli_delete(['vrf'])
self.cli_commit()
def test_openvpn_client_verify(self):
@@ -532,6 +576,46 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
+ def test_openvpn_options(self):
+ # Ensure OpenVPN process restart on openvpn-option CLI node change
+
+ interface = 'vtun5001'
+ path = base_path + [interface]
+
+ self.cli_set(path + ['mode', 'site-to-site'])
+ self.cli_set(path + ['local-address', '10.0.0.2'])
+ self.cli_set(path + ['remote-address', '192.168.0.3'])
+ self.cli_set(path + ['shared-secret-key', 'ovpn_test'])
+
+ self.cli_commit()
+
+ # Now verify the OpenVPN "raw" option passing. Once an openvpn-option is
+ # added, modified or deleted from the CLI, OpenVPN daemon must be restarted
+ cur_pid = process_named_running('openvpn')
+ self.cli_set(path + ['openvpn-option', '--persist-tun'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_set(path + ['openvpn-option', '--persist-key'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_delete(path + ['openvpn-option'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
def test_openvpn_site2site_interfaces_tun(self):
# Create two OpenVPN site-to-site interfaces
diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 4f1e1ee99..8927121a8 100755
--- a/smoketest/scripts/cli/test_interfaces_pppoe.py
+++ b/smoketest/scripts/cli/test_interfaces_pppoe.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2021 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 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
@@ -34,9 +34,12 @@ def get_config_value(interface, key):
# add a classmethod to setup a temporaray PPPoE server for "proper" validation
class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self._interfaces = ['pppoe10', 'pppoe20', 'pppoe30']
- self._source_interface = 'eth0'
+ @classmethod
+ def setUpClass(cls):
+ super(PPPoEInterfaceTest, cls).setUpClass()
+
+ cls._interfaces = ['pppoe10', 'pppoe20', 'pppoe30']
+ cls._source_interface = 'eth0'
def tearDown(self):
# Validate PPPoE client process
@@ -60,7 +63,6 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + [interface, 'authentication', 'user', user])
self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
- self.cli_set(base_path + [interface, 'default-route', 'auto'])
self.cli_set(base_path + [interface, 'mtu', mtu])
self.cli_set(base_path + [interface, 'no-peer-dns'])
@@ -136,7 +138,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos'])
self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos'])
- self.cli_set(base_path + [interface, 'default-route', 'none'])
+ self.cli_set(base_path + [interface, 'no-default-route'])
self.cli_set(base_path + [interface, 'no-peer-dns'])
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf'])
diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py
index adcadc5eb..a51b8d52c 100755
--- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py
@@ -48,7 +48,7 @@ class PEthInterfaceTest(BasicInterfaceTest.TestCase):
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(PEthInterfaceTest, cls).setUpClass()
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py
index 99c25c374..44bfbb5f0 100755
--- a/smoketest/scripts/cli/test_interfaces_tunnel.py
+++ b/smoketest/scripts/cli/test_interfaces_tunnel.py
@@ -42,7 +42,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase):
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(TunnelInterfaceTest, cls).setUpClass()
# create some test interfaces
cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v4 + '/32'])
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index f34b99ea4..058f13721 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -39,7 +39,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(VXLANInterfaceTest, cls).setUpClass()
def test_vxlan_parameters(self):
tos = '40'
@@ -48,7 +48,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase):
for option in self._options.get(intf, []):
self.cli_set(self._base_path + [intf] + option.split())
- self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment'])
+ self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set'])
self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos])
self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)])
ttl += 10
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index aaf27a2c4..f3e9670f7 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -23,10 +23,13 @@ from vyos.configsession import ConfigSessionError
base_path = ['interfaces', 'wireguard']
class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32',
+ @classmethod
+ def setUpClass(cls):
+ super(WireGuardInterfaceTest, cls).setUpClass()
+
+ cls._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32',
'2001:db8:1::ffff/64', '2001:db8:101::1/112']
- self._interfaces = ['wg0', 'wg1']
+ cls._interfaces = ['wg0', 'wg1']
def tearDown(self):
self.cli_delete(base_path)
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 4f539a23c..a24f37d8d 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -48,7 +48,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(WirelessInterfaceTest, cls).setUpClass()
def test_wireless_add_single_ip_address(self):
# derived method to check if member interfaces are enslaved properly
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 2e1b8d431..408facfb3 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -30,7 +30,7 @@ dst_path = base_path + ['destination']
class TestNAT(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestNAT, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -59,36 +59,44 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- tmp = cmd('sudo nft -j list table nat')
+ tmp = cmd('sudo nft -j list chain ip nat POSTROUTING')
data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
for idx in range(0, len(data_json)):
- rule = str(rules[idx])
data = data_json[idx]
- network = f'192.168.{rule}.0/24'
-
- self.assertEqual(data['chain'], 'POSTROUTING')
- self.assertEqual(data['comment'], f'SRC-NAT-{rule}')
- self.assertEqual(data['family'], 'ip')
- self.assertEqual(data['table'], 'nat')
+ if idx == 0:
+ self.assertEqual(data['chain'], 'POSTROUTING')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
- iface = dict_search('match.right', data['expr'][0])
- direction = dict_search('match.left.payload.field', data['expr'][1])
- address = dict_search('match.right.prefix.addr', data['expr'][1])
- mask = dict_search('match.right.prefix.len', data['expr'][1])
-
- if int(rule) < 200:
- self.assertEqual(direction, 'saddr')
- self.assertEqual(iface, outbound_iface_100)
- # check for masquerade keyword
- self.assertIn('masquerade', data['expr'][3])
+ jump_target = dict_search('jump.target', data['expr'][1])
+ self.assertEqual(jump_target,'VYOS_PRE_SNAT_HOOK')
else:
- self.assertEqual(direction, 'daddr')
- self.assertEqual(iface, outbound_iface_200)
- # check for return keyword due to 'exclude'
- self.assertIn('return', data['expr'][3])
-
- self.assertEqual(f'{address}/{mask}', network)
+ rule = str(rules[idx - 1])
+ network = f'192.168.{rule}.0/24'
+
+ self.assertEqual(data['chain'], 'POSTROUTING')
+ self.assertEqual(data['comment'], f'SRC-NAT-{rule}')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ address = dict_search('match.right.prefix.addr', data['expr'][1])
+ mask = dict_search('match.right.prefix.len', data['expr'][1])
+
+ if int(rule) < 200:
+ self.assertEqual(direction, 'saddr')
+ self.assertEqual(iface, outbound_iface_100)
+ # check for masquerade keyword
+ self.assertIn('masquerade', data['expr'][3])
+ else:
+ self.assertEqual(direction, 'daddr')
+ self.assertEqual(iface, outbound_iface_200)
+ # check for return keyword due to 'exclude'
+ self.assertIn('return', data['expr'][3])
+
+ self.assertEqual(f'{address}/{mask}', network)
def test_dnat(self):
rules = ['100', '110', '120', '130', '200', '210', '220', '230']
@@ -111,33 +119,42 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- tmp = cmd('sudo nft -j list table nat')
+ tmp = cmd('sudo nft -j list chain ip nat PREROUTING')
data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
for idx in range(0, len(data_json)):
- rule = str(rules[idx])
data = data_json[idx]
- port = int(f'10{rule}')
-
- self.assertEqual(data['chain'], 'PREROUTING')
- self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}')
- self.assertEqual(data['family'], 'ip')
- self.assertEqual(data['table'], 'nat')
-
- iface = dict_search('match.right', data['expr'][0])
- direction = dict_search('match.left.payload.field', data['expr'][1])
- protocol = dict_search('match.left.payload.protocol', data['expr'][1])
- dnat_addr = dict_search('dnat.addr', data['expr'][3])
- dnat_port = dict_search('dnat.port', data['expr'][3])
-
- self.assertEqual(direction, 'sport')
- self.assertEqual(dnat_addr, '192.0.2.1')
- self.assertEqual(dnat_port, port)
- if int(rule) < 200:
- self.assertEqual(iface, inbound_iface_100)
- self.assertEqual(protocol, inbound_proto_100)
+ if idx == 0:
+ self.assertEqual(data['chain'], 'PREROUTING')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ jump_target = dict_search('jump.target', data['expr'][1])
+ self.assertEqual(jump_target,'VYOS_PRE_DNAT_HOOK')
else:
- self.assertEqual(iface, inbound_iface_200)
+
+ rule = str(rules[idx - 1])
+ port = int(f'10{rule}')
+
+ self.assertEqual(data['chain'], 'PREROUTING')
+ self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ protocol = dict_search('match.left.payload.protocol', data['expr'][1])
+ dnat_addr = dict_search('dnat.addr', data['expr'][3])
+ dnat_port = dict_search('dnat.port', data['expr'][3])
+
+ self.assertEqual(direction, 'sport')
+ self.assertEqual(dnat_addr, '192.0.2.1')
+ self.assertEqual(dnat_port, port)
+ if int(rule) < 200:
+ self.assertEqual(iface, inbound_iface_100)
+ self.assertEqual(protocol, inbound_proto_100)
+ else:
+ self.assertEqual(iface, inbound_iface_200)
def test_snat_required_translation_address(self):
# T2813: Ensure translation address is specified
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 6b7b49792..aac6a30f9 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -32,7 +32,7 @@ dst_path = base_path + ['destination']
class TestNAT66(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestNAT66, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py
index 45a4bd61e..e92123dbc 100755
--- a/smoketest/scripts/cli/test_pki.py
+++ b/smoketest/scripts/cli/test_pki.py
@@ -129,8 +129,13 @@ xGsJxVHfSKeooUQn6q76sg==
"""
class TestPKI(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_delete(base_path)
+ @classmethod
+ def setUpClass(cls):
+ super(TestPKI, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
def tearDown(self):
self.cli_delete(base_path)
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index f1db5350a..6f92457b2 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -154,7 +154,7 @@ peer_group_config = {
class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestProtocolsBGP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -882,5 +882,29 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' rt vpn import {rt_import}', afi_config)
self.assertIn(f' exit-address-family', afi_config)
+ def test_bgp_14_remote_as_peer_group_override(self):
+ # Peer-group member cannot override remote-as of peer-group
+ remote_asn = str(int(ASN) + 150)
+ neighbor = '192.0.2.1'
+ peer_group = 'bar'
+
+ self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn])
+ self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group])
+ self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn])
+
+ # Peer-group member cannot override remote-as of peer-group
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['neighbor', neighbor, 'remote-as'])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'router bgp {ASN}', frrconfig)
+ self.assertIn(f' neighbor {neighbor} peer-group {peer_group}', frrconfig)
+ self.assertIn(f' neighbor {peer_group} peer-group', frrconfig)
+ self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig)
+
if __name__ == '__main__':
- unittest.main(verbosity=2, failfast=True)
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 11c765793..ee4be0b37 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -33,7 +33,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
cls._interfaces = Section.interfaces('ethernet')
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(TestProtocolsISIS, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py
index c6751cc42..76e6ca35a 100755
--- a/smoketest/scripts/cli/test_protocols_mpls.py
+++ b/smoketest/scripts/cli/test_protocols_mpls.py
@@ -68,7 +68,7 @@ profiles = {
class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestProtocolsMPLS, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index e433d06d0..e15ea478b 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -35,7 +35,7 @@ log = logging.getLogger('TestProtocolsOSPF')
class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestProtocolsOSPF, cls).setUpClass()
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
@@ -47,7 +47,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['policy', 'route-map', route_map])
- super(cls, cls).tearDownClass()
+ super(TestProtocolsOSPF, cls).tearDownClass()
def tearDown(self):
# Check for running process
diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py
index 944190089..fa80ad555 100755
--- a/smoketest/scripts/cli/test_protocols_ospfv3.py
+++ b/smoketest/scripts/cli/test_protocols_ospfv3.py
@@ -33,7 +33,7 @@ default_area = '0'
class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestProtocolsOSPFv3, cls).setUpClass()
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit'])
@@ -45,7 +45,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['policy', 'route-map', route_map])
- super(cls, cls).tearDownClass()
+ super(TestProtocolsOSPFv3, cls).tearDownClass()
def tearDown(self):
# Check for running process
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index 3ef9c76d8..19efe7786 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -94,13 +94,13 @@ tables = ['80', '81', '82']
class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestProtocolsStatic, cls).setUpClass()
cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210'])
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['vrf'])
- super(cls, cls).tearDownClass()
+ super(TestProtocolsStatic, cls).tearDownClass()
def tearDown(self):
for route, route_config in routes.items():
diff --git a/smoketest/scripts/cli/test_protocols_static_arp.py b/smoketest/scripts/cli/test_protocols_static_arp.py
new file mode 100755
index 000000000..6663ade96
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_static_arp.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 json
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import cmd
+
+base_path = ['protocols', 'static', 'arp']
+interface = 'eth0'
+address = '192.0.2.1/24'
+
+class TestARP(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestARP, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ # we need a L2 interface with a L3 address to properly configure ARP entries
+ cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', address])
+
+ @classmethod
+ def tearDownClass(cls):
+ # cleanuop L2 interface
+ cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', address])
+ cls.cli_commit(cls)
+
+ super(TestARP, cls).tearDownClass()
+
+ def tearDown(self):
+ # delete test config
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_static_arp(self):
+ test_data = {
+ '192.0.2.10' : { 'lladdr' : '00:01:02:03:04:0a' },
+ '192.0.2.11' : { 'lladdr' : '00:01:02:03:04:0b' },
+ '192.0.2.12' : { 'lladdr' : '00:01:02:03:04:0c' },
+ '192.0.2.13' : { 'lladdr' : '00:01:02:03:04:0d' },
+ '192.0.2.14' : { 'lladdr' : '00:01:02:03:04:0e' },
+ '192.0.2.15' : { 'lladdr' : '00:01:02:03:04:0f' },
+ }
+
+ for host, host_config in test_data.items():
+ self.cli_set(base_path + [host, 'hwaddr', host_config['lladdr']])
+
+ self.cli_commit()
+
+ arp_table = json.loads(cmd('ip -j -4 neigh show'))
+ for host, host_config in test_data.items():
+ # As we search within a list of hosts we need to mark if it was
+ # found or not. This ensures all hosts from test_data are processed
+ found = False
+ for entry in arp_table:
+ # Other ARP entry - not related to this testcase
+ if entry['dst'] not in list(test_data):
+ continue
+
+ if entry['dst'] == host:
+ self.assertEqual(entry['lladdr'], host_config['lladdr'])
+ self.assertEqual(entry['dev'], interface)
+ found = True
+
+ if found == False:
+ print(entry)
+ self.assertTrue(found)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 9adb9c042..9c9d6d9f1 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -38,7 +38,7 @@ domain_name = 'vyos.net'
class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestServiceDHCPServer, cls).setUpClass()
cidr_mask = subnet.split('/')[-1]
cls.cli_set(cls, ['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}'])
@@ -46,7 +46,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'dummy', 'dum8765'])
- super(cls, cls).tearDownClass()
+ super(TestServiceDHCPServer, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 7177f1505..f83453323 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -32,16 +32,24 @@ dns_1 = '2001:db8::1'
dns_2 = '2001:db8::2'
domain = 'vyos.net'
nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2']
-interface = 'eth1'
+interface = 'eth0'
interface_addr = inc_ip(subnet, 1) + '/64'
-class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(['interfaces', 'ethernet', interface, 'address', interface_addr])
+class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestServiceDHCPv6Server, cls).setUpClass()
+ cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr])
+ cls.cli_commit(cls)
+
+ super(TestServiceDHCPv6Server, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
- self.cli_delete(['interfaces', 'ethernet', interface, 'address', interface_addr])
self.cli_commit()
def test_single_pool(self):
diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py
index ddb42e8f8..18f1b8ec5 100755
--- a/smoketest/scripts/cli/test_service_ids.py
+++ b/smoketest/scripts/cli/test_service_ids.py
@@ -30,7 +30,7 @@ base_path = ['service', 'ids', 'ddos-protection']
class TestServiceIDS(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestServiceIDS, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py
index 64fdd9d1b..439c96c33 100755
--- a/smoketest/scripts/cli/test_service_lldp.py
+++ b/smoketest/scripts/cli/test_service_lldp.py
@@ -37,7 +37,7 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(TestServiceLLDP, cls).setUpClass()
# create a test interfaces
for addr in mgmt_addr:
@@ -50,7 +50,7 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'dummy', mgmt_if])
- super().tearDownClass()
+ super(TestServiceLLDP, cls).tearDownClass()
def tearDown(self):
# service must be running after it was configured
diff --git a/smoketest/scripts/cli/test_service_salt.py b/smoketest/scripts/cli/test_service_salt.py
new file mode 100755
index 000000000..00a4f2020
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_salt.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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 unittest
+
+from socket import gethostname
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import process_named_running
+from vyos.util import read_file
+from vyos.util import cmd
+
+PROCESS_NAME = 'salt-minion'
+SALT_CONF = '/etc/salt/minion'
+base_path = ['service', 'salt-minion']
+
+interface = 'dum4456'
+
+class TestServiceSALT(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestServiceSALT, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16'])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'dummy', interface])
+ super(TestServiceSALT, cls).tearDownClass()
+
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ # delete testing SALT config
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # For an unknown reason on QEMU systems (e.g. where smoketests are executed
+ # from the CI) salt-minion process is not killed by systemd. Apparently
+ # no issue on VMWare.
+ if cmd('systemd-detect-virt') != 'kvm':
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_default(self):
+ servers = ['192.0.2.1', '192.0.2.2']
+
+ for server in servers:
+ self.cli_set(base_path + ['master', server])
+
+ self.cli_commit()
+
+ # commiconf = read_file() Check configured port
+ conf = read_file(SALT_CONF)
+ self.assertIn(f' - {server}', conf)
+
+ # defaults
+ hostname = gethostname()
+ self.assertIn(f'hash_type: sha256', conf)
+ self.assertIn(f'id: {hostname}', conf)
+ self.assertIn(f'mine_interval: 60', conf)
+
+ def test_options(self):
+ server = '192.0.2.3'
+ hash = 'sha1'
+ id = 'foo'
+ interval = '120'
+
+ self.cli_set(base_path + ['master', server])
+ self.cli_set(base_path + ['hash', hash])
+ self.cli_set(base_path + ['id', id])
+ self.cli_set(base_path + ['interval', interval])
+ self.cli_set(base_path + ['source-interface', interface])
+
+ self.cli_commit()
+
+ # commiconf = read_file() Check configured port
+ conf = read_file(SALT_CONF)
+ self.assertIn(f'- {server}', conf)
+
+ # defaults
+ self.assertIn(f'hash_type: {hash}', conf)
+ self.assertIn(f'id: {id}', conf)
+ self.assertIn(f'mine_interval: {interval}', conf)
+ self.assertIn(f'source_interface_name: {interface}', conf)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py
index fc24fd54e..e80c689cc 100755
--- a/smoketest/scripts/cli/test_service_snmp.py
+++ b/smoketest/scripts/cli/test_service_snmp.py
@@ -49,7 +49,7 @@ def get_config_value(key):
class TestSNMPService(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestSNMPService, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 9ed263655..77ad5bc0d 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -46,7 +46,7 @@ def get_config_value(key):
class TestServiceSSH(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestServiceSSH, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py
index c3e9b600f..e4df88c1e 100755
--- a/smoketest/scripts/cli/test_service_upnp.py
+++ b/smoketest/scripts/cli/test_service_upnp.py
@@ -37,7 +37,7 @@ ipv6_addr = '2001:db8::1/64'
class TestServiceUPnP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestServiceUPnP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
@@ -51,7 +51,7 @@ class TestServiceUPnP(VyOSUnitTestSHIM.TestCase):
cls.cli_delete(cls, address_base)
cls._session.commit()
- super(cls, cls).tearDownClass()
+ super(TestServiceUPnP, cls).tearDownClass()
def tearDown(self):
# Check for running process
diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py
index ebbd9fe55..772d6ab16 100755
--- a/smoketest/scripts/cli/test_service_webproxy.py
+++ b/smoketest/scripts/cli/test_service_webproxy.py
@@ -33,14 +33,14 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(TestServiceWebProxy, cls).setUpClass()
# create a test interfaces
cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32'])
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
- super().tearDownClass()
+ super(TestServiceWebProxy, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py
index 84f17bcb0..5a73ebc7d 100755
--- a/smoketest/scripts/cli/test_system_flow-accounting.py
+++ b/smoketest/scripts/cli/test_system_flow-accounting.py
@@ -32,7 +32,7 @@ uacctd_conf = '/run/pmacct/uacctd.conf'
class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestSystemFlowAccounting, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index c8cf04b7d..e2821687c 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -31,7 +31,7 @@ base_path = ['system', 'ntp']
class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestSystemNTP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 1338fe81c..8a6514d57 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -114,7 +114,7 @@ rgiyCHemtMepq57Pl1Nmj49eEA==
class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestVPNIPsec, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
@@ -123,8 +123,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
- super(cls, cls).tearDownClass()
-
+ super(TestVPNIPsec, cls).tearDownClass()
cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}'])
def setUp(self):
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index 1f2c36f0d..bda279342 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -24,8 +24,27 @@ OCSERV_CONF = '/run/ocserv/ocserv.conf'
base_path = ['vpn', 'openconnect']
pki_path = ['pki']
-cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
-key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww'
+
+cert_data = """
+MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw
+WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
+bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx
+MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV
+BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP
+UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3
+QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu
++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz
+ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93
++dm/LDnp7C0=
+"""
+
+key_data = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx
+2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7
+u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww
+"""
class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
@@ -42,16 +61,16 @@ class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase):
self.cli_delete(pki_path)
self.cli_delete(base_path)
- self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data])
- self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data])
- self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data])
+ self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')])
+ self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')])
+ self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')])
- self.cli_set(base_path + ["authentication", "local-users", "username", user, "password", password])
- self.cli_set(base_path + ["authentication", "local-users", "username", user, "otp", "key", otp])
- self.cli_set(base_path + ["authentication", "mode", "local", "password-otp"])
- self.cli_set(base_path + ["network-settings", "client-ip-settings", "subnet", "192.0.2.0/24"])
- self.cli_set(base_path + ["ssl", "ca-certificate", 'openconnect'])
- self.cli_set(base_path + ["ssl", "certificate", 'openconnect'])
+ self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'password', password])
+ self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'otp', 'key', otp])
+ self.cli_set(base_path + ['authentication', 'mode', 'local', 'password-otp'])
+ self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', '192.0.2.0/24'])
+ self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect'])
+ self.cli_set(base_path + ['ssl', 'certificate', 'openconnect'])
self.cli_commit()
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index c591d6cf5..ff18f7261 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -49,7 +49,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
if not '.' in tmp:
cls._interfaces.append(tmp)
# call base-classes classmethod
- super(cls, cls).setUpClass()
+ super(VRFTest, cls).setUpClass()
def tearDown(self):
# delete all VRFs
diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py
index 6e34f3179..2c580e2f1 100755
--- a/smoketest/scripts/cli/test_zone_policy.py
+++ b/smoketest/scripts/cli/test_zone_policy.py
@@ -23,13 +23,13 @@ from vyos.util import cmd
class TestZonePolicy(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
- super(cls, cls).setUpClass()
+ super(TestZonePolicy, cls).setUpClass()
cls.cli_set(cls, ['firewall', 'name', 'smoketest', 'default-action', 'drop'])
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['firewall'])
- super(cls, cls).tearDownClass()
+ super(TestZonePolicy, cls).tearDownClass()
def tearDown(self):
self.cli_delete(['zone-policy'])
diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py
index aac07bd80..51a08bee5 100755
--- a/src/conf_mode/arp.py
+++ b/src/conf_mode/arp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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,92 +13,54 @@
#
# 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 syslog as sl
+from sys import exit
from vyos.config import Config
+from vyos.configdict import node_changed
from vyos.util import call
from vyos import ConfigError
-
from vyos import airbag
airbag.enable()
-arp_cmd = '/usr/sbin/arp'
-
-def get_config():
- c = Config()
- if not c.exists('protocols static arp'):
- return None
-
- c.set_level('protocols static')
- config_data = {}
-
- for ip_addr in c.list_nodes('arp'):
- config_data.update(
- {
- ip_addr : c.return_value('arp ' + ip_addr + ' hwaddr')
- }
- )
-
- return config_data
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
-def generate(c):
- c_eff = Config()
- c_eff.set_level('protocols static')
- c_eff_cnf = {}
- for ip_addr in c_eff.list_effective_nodes('arp'):
- c_eff_cnf.update(
- {
- ip_addr : c_eff.return_effective_value('arp ' + ip_addr + ' hwaddr')
- }
- )
+ base = ['protocols', 'static', 'arp']
+ arp = conf.get_config_dict(base)
+ tmp = node_changed(conf, base)
+ if tmp: arp.update({'removed' : node_changed(conf, base)})
- config_data = {
- 'remove' : [],
- 'update' : {}
- }
- ### removal
- if c == None:
- for ip_addr in c_eff_cnf:
- config_data['remove'].append(ip_addr)
- else:
- for ip_addr in c_eff_cnf:
- if not ip_addr in c or c[ip_addr] == None:
- config_data['remove'].append(ip_addr)
+ return arp
- ### add/update
- if c != None:
- for ip_addr in c:
- if not ip_addr in c_eff_cnf:
- config_data['update'][ip_addr] = c[ip_addr]
- if ip_addr in c_eff_cnf:
- if c[ip_addr] != c_eff_cnf[ip_addr] and c[ip_addr] != None:
- config_data['update'][ip_addr] = c[ip_addr]
+def verify(arp):
+ pass
- return config_data
+def generate(arp):
+ pass
-def apply(c):
- for ip_addr in c['remove']:
- sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr)
- call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1')
+def apply(arp):
+ if not arp:
+ return None
- for ip_addr in c['update']:
- sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr])
- updated = c['update'][ip_addr]
- call(f'{arp_cmd} -s {ip_addr} {updated}')
+ if 'removed' in arp:
+ for host in arp['removed']:
+ call(f'arp --delete {host}')
+ if 'arp' in arp:
+ for host, host_config in arp['arp'].items():
+ mac = host_config['hwaddr']
+ call(f'arp --set {host} {mac}')
if __name__ == '__main__':
- try:
- c = get_config()
- ## syntax verification is done via cli
- config = generate(c)
- apply(config)
- except ConfigError as e:
- print(e)
- sys.exit(1)
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py
index 516671844..3c1a61f19 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/containers.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -28,7 +28,6 @@ from vyos.configdict import node_changed
from vyos.util import call
from vyos.util import cmd
from vyos.util import run
-from vyos.util import read_file
from vyos.util import write_file
from vyos.template import inc_ip
from vyos.template import is_ipv4
@@ -77,10 +76,10 @@ def get_config(config=None):
container['name'][name] = dict_merge(default_values, container['name'][name])
# Delete container network, delete containers
- tmp = node_changed(conf, ['container', 'network'])
+ tmp = node_changed(conf, base + ['container', 'network'])
if tmp: container.update({'network_remove' : tmp})
- tmp = node_changed(conf, ['container', 'name'])
+ tmp = node_changed(conf, base + ['container', 'name'])
if tmp: container.update({'container_remove' : tmp})
return container
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index 6352e0b4a..4de2ca2f3 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -66,18 +66,19 @@ def generate(relay):
if not relay:
return None
- render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay)
return None
def apply(relay):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-relay.service'
if not relay:
- call('systemctl stop isc-dhcp-relay.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart isc-dhcp-relay.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index d27f8d995..52b682d6d 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -286,7 +286,7 @@ def generate(dhcp):
# Please see: https://phabricator.vyos.net/T1129 for quoting of the raw
# parameters we can pass to ISC DHCPd
tmp_file = '/tmp/dhcpd.conf'
- render(tmp_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
# XXX: as we have the ability for a user to pass in "raw" options via VyOS
# CLI (see T3544) we now ask ISC dhcpd to test the newly rendered
@@ -299,7 +299,7 @@ def generate(dhcp):
# Now that we know that the newly rendered configuration is "good" we can
# render the "real" configuration
- render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp,
+ render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
return None
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index aea2c3b73..c1bd51f62 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -82,19 +82,20 @@ def generate(relay):
if not relay:
return None
- render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay)
+ render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay)
return None
def apply(relay):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-relay6.service'
if not relay:
# DHCPv6 relay support is removed in the commit
- call('systemctl stop isc-dhcp-relay6.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
return None
- call('systemctl restart isc-dhcp-relay6.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index be1e6db1e..078ff327c 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -41,7 +41,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
+ dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
return dhcpv6
def verify(dhcpv6):
@@ -51,7 +53,7 @@ def verify(dhcpv6):
# If DHCP is enabled we need one share-network
if 'shared_network_name' not in dhcpv6:
- raise ConfigError('No DHCPv6 shared networks configured. At least\n' \
+ raise ConfigError('No DHCPv6 shared networks configured. At least '\
'one DHCPv6 shared network must be configured.')
# Inspect shared-network/subnet
@@ -60,8 +62,9 @@ def verify(dhcpv6):
for network, network_config in dhcpv6['shared_network_name'].items():
# A shared-network requires a subnet definition
if 'subnet' not in network_config:
- raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". At least one\n' \
- 'lease subnet must be configured for each shared network!')
+ raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\
+ 'At least one lease subnet must be configured for '\
+ 'each shared network!')
for subnet, subnet_config in network_config['subnet'].items():
if 'address_range' in subnet_config:
@@ -83,20 +86,20 @@ def verify(dhcpv6):
# Stop address must be greater or equal to start address
if not ip_address(stop) >= ip_address(start):
- raise ConfigError(f'address-range stop address "{stop}" must be greater or equal\n' \
+ raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \
f'to the range start address "{start}"!')
# DHCPv6 range start address must be unique - two ranges can't
# start with the same address - makes no sense
if start in range6_start:
- raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \
+ raise ConfigError(f'Conflicting DHCPv6 lease range: '\
f'Pool start address "{start}" defined multipe times!')
range6_start.append(start)
# DHCPv6 range stop address must be unique - two ranges can't
# end with the same address - makes no sense
if stop in range6_stop:
- raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \
+ raise ConfigError(f'Conflicting DHCPv6 lease range: '\
f'Pool stop address "{stop}" defined multipe times!')
range6_stop.append(stop)
@@ -112,7 +115,7 @@ def verify(dhcpv6):
for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items():
if 'stop' not in prefix_config:
- raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}"\n'
+ raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\
f'must be configured')
if 'prefix_length' not in prefix_config:
@@ -126,6 +129,10 @@ def verify(dhcpv6):
if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet):
raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!')
+ if 'vendor_option' in subnet_config:
+ if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2:
+ raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!')
+
# Subnets must be unique
if subnet in subnets:
raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!')
@@ -149,8 +156,8 @@ def verify(dhcpv6):
raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2))
if not listen_ok:
- raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on\n' \
- 'this machine. At least one subnet6 must be connected such that\n' \
+ raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\
+ 'this machine. At least one subnet6 must be connected such that '\
'DHCPv6 listens on an interface!')
@@ -161,20 +168,20 @@ def generate(dhcpv6):
if not dhcpv6 or 'disable' in dhcpv6:
return None
- render(config_file, 'dhcp-server/dhcpdv6.conf.tmpl', dhcpv6)
+ render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6)
return None
def apply(dhcpv6):
# bail out early - looks like removal from running config
+ service_name = 'isc-dhcp-server6.service'
if not dhcpv6 or 'disable' in dhcpv6:
# DHCP server is removed in the commit
- call('systemctl stop isc-dhcp-server6.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
-
return None
- call('systemctl restart isc-dhcp-server6.service')
+ call(f'systemctl restart {service_name}')
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index fa9b21f20..f1c2d1f43 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -279,10 +279,10 @@ def generate(dns):
if not dns:
return None
- render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl',
+ render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2',
dns, user=pdns_rec_user, group=pdns_rec_group)
- render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl',
+ render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2',
dns, user=pdns_rec_user, group=pdns_rec_group)
for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'):
@@ -290,7 +290,7 @@ def generate(dns):
if 'authoritative_zones' in dns:
for zone in dns['authoritative_zones']:
- render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl',
+ render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2',
zone, user=pdns_rec_user, group=pdns_rec_group)
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index a31e5ed75..06a2f7e15 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -131,7 +131,7 @@ def generate(dyndns):
if not dyndns:
return None
- render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns)
+ render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns)
return None
def apply(dyndns):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index f33198a49..de78d53a8 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -21,6 +21,7 @@ from glob import glob
from json import loads
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
@@ -225,7 +226,7 @@ def verify_rule(firewall, rule_conf, ipv6):
raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
if not group_obj:
- print(f'WARNING: {error_group} "{group_name}" has no members')
+ Warning(f'{error_group} "{group_name}" has no members!')
if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
if 'protocol' not in rule_conf:
@@ -395,7 +396,7 @@ def resync_policy_route():
# Update policy route as firewall groups were updated
tmp = run(policy_route_conf_script)
if tmp > 0:
- print('Warning: Failed to re-apply policy route configuration')
+ Warning('Failed to re-apply policy route configuration!')
def apply(firewall):
if 'first_install' in firewall:
diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 87bad0dc6..93f244f42 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -21,13 +21,14 @@ import copy
import vyos.util
import vyos.hostsd_client
-from vyos import ConfigError
+from vyos.base import Warning
from vyos.config import Config
from vyos.ifconfig import Section
from vyos.template import is_ip
from vyos.util import cmd
from vyos.util import call
from vyos.util import process_named_running
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -113,7 +114,7 @@ def verify(hosts):
for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items():
# Warnin user if interface does not have DHCP or DHCPv6 configured
if not set(interface_config).intersection(['dhcp', 'dhcpv6']):
- print(f'WARNING: "{interface}" is not a DHCP interface but uses DHCP name-server option!')
+ Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!')
return None
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index fb030c9f3..37df3dc92 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -92,7 +93,7 @@ def generate(igmp_proxy):
# bail out early - service is disabled, but inform user
if 'disable' in igmp_proxy:
- print('WARNING: IGMP Proxy will be deactivated because it is disabled')
+ Warning('IGMP Proxy will be deactivated because it is disabled')
return None
render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy)
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index ad5a0f499..4167594e3 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -68,7 +68,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bonding']
- bond = get_interface_dict(conf, base)
+ ifname, bond = get_interface_dict(conf, base)
# To make our own life easier transfor the list of member interfaces
# into a dictionary - we will use this to add additional information
@@ -81,14 +81,14 @@ def get_config(config=None):
if 'mode' in bond:
bond['mode'] = get_bond_mode(bond['mode'])
- tmp = leaf_node_changed(conf, ['mode'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'mode'])
if tmp: bond.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['lacp-rate'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate'])
if tmp: bond.update({'shutdown_required': {}})
# determine which members have been removed
- interfaces_removed = leaf_node_changed(conf, ['member', 'interface'])
+ interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface'])
if interfaces_removed:
bond.update({'shutdown_required': {}})
if 'member' not in bond:
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index b1f7e6d7c..38ae727c1 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -50,15 +50,15 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bridge']
- bridge = get_interface_dict(conf, base)
+ ifname, bridge = get_interface_dict(conf, base)
# determine which members have been removed
- tmp = node_changed(conf, ['member', 'interface'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + [ifname, 'member', 'interface'], key_mangling=('-', '_'))
if tmp:
if 'member' in bridge:
- bridge['member'].update({'interface_remove': tmp })
+ bridge['member'].update({'interface_remove' : tmp })
else:
- bridge.update({'member': {'interface_remove': tmp }})
+ bridge.update({'member' : {'interface_remove' : tmp }})
if dict_search('member.interface', bridge):
# XXX: T2665: we need a copy of the dict keys for iteration, else we will get:
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index 4a1eb7b93..e771581e1 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -37,7 +37,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'dummy']
- dummy = get_interface_dict(conf, base)
+ _, dummy = get_interface_dict(conf, base)
return dummy
def verify(dummy):
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 68f59893a..fec4456fb 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -19,6 +19,7 @@ import os
from glob import glob
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
@@ -64,7 +65,7 @@ def get_config(config=None):
get_first_key=True, no_tag_node_value_mangle=True)
base = ['interfaces', 'ethernet']
- ethernet = get_interface_dict(conf, base)
+ _, ethernet = get_interface_dict(conf, base)
if 'deleted' not in ethernet:
if pki: ethernet['pki'] = pki
@@ -142,8 +143,8 @@ def verify(ethernet):
raise ConfigError('XDP requires additional TX queues, too few available!')
if {'is_bond_member', 'mac'} <= set(ethernet):
- print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" '
- f'is a member of bond "{is_bond_member}"'.format(**ethernet))
+ Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \
+ f'is a member of bond "{is_bond_member}"'.format(**ethernet))
# use common function to verify VLAN configuration
verify_vlan_config(ethernet)
diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py
index 3a668226b..b9cf2fa3c 100755
--- a/src/conf_mode/interfaces-geneve.py
+++ b/src/conf_mode/interfaces-geneve.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 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,6 +21,8 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
@@ -41,7 +43,18 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'geneve']
- geneve = get_interface_dict(conf, base)
+ ifname, geneve = get_interface_dict(conf, base)
+
+ # GENEVE interfaces are picky and require recreation if certain parameters
+ # change. But a GENEVE interface should - of course - not be re-created if
+ # it's description or IP address is adjusted. Feels somehow logic doesn't it?
+ for cli_option in ['remote', 'vni']:
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
+ geneve.update({'rebuild_required': {}})
+
+ if is_node_changed(conf, base + [ifname, 'parameters']):
+ geneve.update({'rebuild_required': {}})
+
return geneve
def verify(geneve):
@@ -67,11 +80,12 @@ def generate(geneve):
def apply(geneve):
# Check if GENEVE interface already exists
- if geneve['ifname'] in interfaces():
- g = GeneveIf(geneve['ifname'])
- # GENEVE is super picky and the tunnel always needs to be recreated,
- # thus we can simply always delete it first.
- g.remove()
+ if 'rebuild_required' in geneve or 'delete' in geneve:
+ if geneve['ifname'] in interfaces():
+ g = GeneveIf(geneve['ifname'])
+ # GENEVE is super picky and the tunnel always needs to be recreated,
+ # thus we can simply always delete it first.
+ g.remove()
if 'deleted' not in geneve:
# Finally create the new interface
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 22256bf4f..6a486f969 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -45,15 +45,15 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'l2tpv3']
- l2tpv3 = get_interface_dict(conf, base)
+ ifname, l2tpv3 = get_interface_dict(conf, base)
# To delete an l2tpv3 interface we need the current tunnel and session-id
if 'deleted' in l2tpv3:
- tmp = leaf_node_changed(conf, ['tunnel-id'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id'])
# leaf_node_changed() returns a list
l2tpv3.update({'tunnel_id': tmp[0]})
- tmp = leaf_node_changed(conf, ['session-id'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'session-id'])
l2tpv3.update({'session_id': tmp[0]})
return l2tpv3
diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py
index e4bc15bb5..08d34477a 100755
--- a/src/conf_mode/interfaces-loopback.py
+++ b/src/conf_mode/interfaces-loopback.py
@@ -36,7 +36,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'loopback']
- loopback = get_interface_dict(conf, base)
+ _, loopback = get_interface_dict(conf, base)
return loopback
def verify(loopback):
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index c71863e61..279dd119b 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -48,7 +48,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'macsec']
- macsec = get_interface_dict(conf, base)
+ ifname, macsec = get_interface_dict(conf, base)
# Check if interface has been removed
if 'deleted' in macsec:
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 83d1c6d9b..4750ca3e8 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -32,7 +32,7 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
@@ -85,13 +85,12 @@ def get_config(config=None):
tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- openvpn = get_interface_dict(conf, base)
+ ifname, openvpn = get_interface_dict(conf, base)
if 'deleted' not in openvpn:
openvpn['pki'] = tmp_pki
-
- tmp = leaf_node_changed(conf, ['openvpn-option'])
- if tmp: openvpn['restart_required'] = ''
+ if is_node_changed(conf, base + [ifname, 'openvpn-option']):
+ openvpn.update({'restart_required': {}})
# We have to get the dict using 'get_config_dict' instead of 'get_interface_dict'
# as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there.
@@ -608,7 +607,7 @@ def generate(openvpn):
# Generate User/Password authentication file
if 'authentication' in openvpn:
- render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn,
+ render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn,
user=user, group=group, permission=0o600)
else:
# delete old auth file if present
@@ -624,16 +623,16 @@ def generate(openvpn):
# Our client need's to know its subnet mask ...
client_config['server_subnet'] = dict_search('server.subnet', openvpn)
- render(client_file, 'openvpn/client.conf.tmpl', client_config,
+ render(client_file, 'openvpn/client.conf.j2', client_config,
user=user, group=group)
# we need to support quoting of raw parameters from OpenVPN CLI
# see https://phabricator.vyos.net/T1632
- render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn,
+ render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn,
formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
# Render 20-override.conf for OpenVPN service
- render(service_file.format(**openvpn), 'openvpn/service-override.conf.tmpl', openvpn,
+ render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn,
formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
# Reload systemd services config to apply an override
call(f'systemctl daemon-reload')
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index bfb1fadd5..26daa8381 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -22,7 +22,9 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_authentication
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
@@ -47,33 +49,17 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'pppoe']
- pppoe = get_interface_dict(conf, base)
+ ifname, pppoe = get_interface_dict(conf, base)
# We should only terminate the PPPoE session if critical parameters change.
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
- tmp = leaf_node_changed(conf, ['access-concentrator'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['connect-on-demand'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['service-name'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['source-interface'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['vrf'])
- # leaf_node_changed() returns a list, as VRF is a non-multi node, there
- # will be only one list element
- if tmp: pppoe.update({'vrf_old': tmp[0]})
-
- tmp = leaf_node_changed(conf, ['authentication', 'user'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['authentication', 'password'])
- if tmp: pppoe.update({'shutdown_required': {}})
+ for options in ['access-concentrator', 'connect-on-demand', 'service-name',
+ 'source-interface', 'vrf', 'no-default-route', 'authentication']:
+ if is_node_changed(conf, base + [ifname, options]):
+ pppoe.update({'shutdown_required': {}})
+ # bail out early - no need to further process other nodes
+ break
return pppoe
@@ -120,7 +106,7 @@ def apply(pppoe):
return None
# reconnect should only be necessary when certain config options change,
- # like ACS name, authentication, no-peer-dns, source-interface
+ # like ACS name, authentication ... (see get_config() for details)
if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
'shutdown_required' in pppoe):
@@ -130,6 +116,9 @@ def apply(pppoe):
p.remove()
call(f'systemctl restart ppp@{ifname}.service')
+ # When interface comes "live" a hook is called:
+ # /etc/ppp/ip-up.d/99-vyos-pppoe-callback
+ # which triggers PPPoEIf.update()
else:
if os.path.isdir(f'/sys/class/net/{ifname}'):
p = PPPoEIf(ifname)
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index f2c85554f..1cd3fe276 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -18,7 +18,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -42,14 +42,14 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'pseudo-ethernet']
- peth = get_interface_dict(conf, base)
+ ifname, peth = get_interface_dict(conf, base)
- mode = leaf_node_changed(conf, ['mode'])
- if mode: peth.update({'mode_old' : mode})
+ mode = is_node_changed(conf, ['mode'])
+ if mode: peth.update({'shutdown_required' : {}})
if 'source_interface' in peth:
- peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
- peth['source_interface'])
+ _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'],
+ peth['source_interface'])
return peth
def verify(peth):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f4668d976..eff7f373c 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -48,10 +48,10 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'tunnel']
- tunnel = get_interface_dict(conf, base)
+ ifname, tunnel = get_interface_dict(conf, base)
if 'deleted' not in tunnel:
- tmp = leaf_node_changed(conf, ['encapsulation'])
+ tmp = leaf_node_changed(conf, base + [ifname, 'encapsulation'])
if tmp: tunnel.update({'encapsulation_changed': {}})
# We also need to inspect other configured tunnels as there are Kernel
diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py
index f06fdff1b..f4b0436af 100755
--- a/src/conf_mode/interfaces-vti.py
+++ b/src/conf_mode/interfaces-vti.py
@@ -36,7 +36,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'vti']
- vti = get_interface_dict(conf, base)
+ _, vti = get_interface_dict(conf, base)
return vti
def verify(vti):
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 0a9b51cac..f44d754ba 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -19,9 +19,11 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
@@ -44,18 +46,19 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'vxlan']
- vxlan = get_interface_dict(conf, base)
+ ifname, vxlan = get_interface_dict(conf, base)
# VXLAN interfaces are picky and require recreation if certain parameters
# change. But a VXLAN interface should - of course - not be re-created if
# it's description or IP address is adjusted. Feels somehow logic doesn't it?
for cli_option in ['external', 'gpe', 'group', 'port', 'remote',
- 'source-address', 'source-interface', 'vni',
- 'parameters ip dont-fragment', 'parameters ip tos',
- 'parameters ip ttl']:
- if leaf_node_changed(conf, cli_option.split()):
+ 'source-address', 'source-interface', 'vni']:
+ if leaf_node_changed(conf, base + [ifname, cli_option]):
vxlan.update({'rebuild_required': {}})
+ if is_node_changed(conf, base + [ifname, 'parameters']):
+ vxlan.update({'rebuild_required': {}})
+
# We need to verify that no other VXLAN tunnel is configured when external
# mode is in use - Linux Kernel limitation
conf.set_level(base)
@@ -78,7 +81,7 @@ def verify(vxlan):
return None
if int(vxlan['mtu']) < 1500:
- print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
+ Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
if 'group' in vxlan:
if 'source_interface' not in vxlan:
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index b404375d6..180ffa507 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -46,17 +46,17 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'wireguard']
- wireguard = get_interface_dict(conf, base)
+ ifname, wireguard = get_interface_dict(conf, base)
# Check if a port was changed
- wireguard['port_changed'] = leaf_node_changed(conf, ['port'])
+ wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port'])
# Determine which Wireguard peer has been removed.
# Peers can only be removed with their public key!
dict = {}
- tmp = node_changed(conf, ['peer'], key_mangling=('-', '_'))
+ tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_'))
for peer in (tmp or []):
- public_key = leaf_node_changed(conf, ['peer', peer, 'public_key'])
+ public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key'])
if public_key:
dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict)
wireguard.update(dict)
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 7fc22cdab..d34297063 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -76,15 +76,19 @@ def get_config(config=None):
conf = Config()
base = ['interfaces', 'wireless']
- wifi = get_interface_dict(conf, base)
+ ifname, wifi = get_interface_dict(conf, base)
# Cleanup "delete" default values when required user selectable values are
# not defined at all
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'),
+ get_first_key=True)
if not (dict_search('security.wpa.passphrase', tmp) or
dict_search('security.wpa.radius', tmp)):
if 'deleted' not in wifi:
del wifi['security']['wpa']
+ # if 'security' key is empty, drop it too
+ if len(wifi['security']) == 0:
+ del wifi['security']
# defaults include RADIUS server specifics per TAG node which need to be
# added to individual RADIUS servers instead - so we can simply delete them
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index 9a33039a3..e275ace84 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -21,7 +21,7 @@ from time import sleep
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_authentication
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_mirror_redirect
@@ -50,42 +50,36 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'wwan']
- wwan = get_interface_dict(conf, base)
+ ifname, wwan = get_interface_dict(conf, base)
# We should only terminate the WWAN session if critical parameters change.
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
- tmp = leaf_node_changed(conf, ['address'])
+ tmp = is_node_changed(conf, base + [ifname, 'address'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['apn'])
+ tmp = is_node_changed(conf, base + [ifname, 'apn'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['disable'])
+ tmp = is_node_changed(conf, base + [ifname, 'disable'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['vrf'])
- # leaf_node_changed() returns a list, as VRF is a non-multi node, there
- # will be only one list element
- if tmp: wwan.update({'vrf_old': tmp[0]})
-
- tmp = leaf_node_changed(conf, ['authentication', 'user'])
+ tmp = is_node_changed(conf, base + [ifname, 'vrf'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['authentication', 'password'])
+ tmp = is_node_changed(conf, base + [ifname, 'authentication'])
if tmp: wwan.update({'shutdown_required': {}})
- tmp = leaf_node_changed(conf, ['ipv6', 'address', 'autoconf'])
+ tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf'])
if tmp: wwan.update({'shutdown_required': {}})
# We need to know the amount of other WWAN interfaces as ModemManager needs
# to be started or stopped.
conf.set_level(base)
- wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
- ifname = wwan['ifname']
# This if-clause is just to be sure - it will always evaluate to true
if ifname in wwan['other_interfaces']:
del wwan['other_interfaces'][ifname]
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index db8328259..2bb615eb7 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.validate import is_addr_assigned
@@ -84,11 +85,11 @@ def verify(lldp):
if 'management_address' in lldp:
for address in lldp['management_address']:
- message = f'WARNING: LLDP management address "{address}" is invalid'
+ message = f'LLDP management address "{address}" is invalid'
if is_loopback_addr(address):
- print(f'{message} - loopback address')
+ Warning(f'{message} - loopback address')
elif not is_addr_assigned(address):
- print(f'{message} - not assigned to any interface')
+ Warning(f'{message} - not assigned to any interface')
if 'interface' in lldp:
for interface, interface_config in lldp['interface'].items():
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 14ca7bc94..8aaebf9ff 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -23,6 +23,7 @@ from platform import release as kernel_version
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -142,14 +143,14 @@ def verify(nat):
raise ConfigError(f'{err_msg} outbound-interface not specified')
if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
if addr != 'masquerade' and not is_ip_network(addr):
for ip in addr.split('-'):
if not is_addr_assigned(ip):
- print(f'WARNING: IP address {ip} does not exist on the system!')
+ Warning(f'IP address {ip} does not exist on the system!')
elif 'exclude' not in config:
raise ConfigError(f'{err_msg}\n' \
'translation address not specified')
@@ -167,7 +168,7 @@ def verify(nat):
'inbound-interface not specified')
else:
if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
if dict_search('translation.address', config) == None and 'exclude' not in config:
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 8bf2e8073..1cd15811f 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -21,6 +21,7 @@ import os
from sys import exit
from netifaces import interfaces
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
@@ -117,12 +118,12 @@ def verify(nat):
raise ConfigError(f'{err_msg} outbound-interface not specified')
if config['outbound_interface'] not in interfaces():
- raise ConfigError(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
addr = dict_search('translation.address', config)
if addr != None:
if addr != 'masquerade' and not is_ipv6(addr):
- raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address')
+ raise ConfigError(f'IPv6 address {addr} is not a valid address')
else:
raise ConfigError(f'{err_msg} translation address not specified')
@@ -140,7 +141,7 @@ def verify(nat):
'inbound-interface not specified')
else:
if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
- print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
return None
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 3d1d7d8c5..09d181d43 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -20,6 +20,7 @@ import re
from json import loads
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.template import render
from vyos.util import cmd
@@ -135,7 +136,7 @@ def verify_rule(policy, name, rule_conf, ipv6):
raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule')
if not group_obj:
- print(f'WARNING: {error_group} "{group_name}" has no members')
+ Warning(f'{error_group} "{group_name}" has no members')
if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
if 'protocol' not in rule_conf:
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index f6d5071c2..a9173ab87 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -138,13 +138,20 @@ def verify(bgp):
if asn == bgp['local_as']:
raise ConfigError('Cannot have local-as same as BGP AS number')
+ # Neighbor AS specified for local-as and remote-as can not be the same
+ if dict_search('remote_as', peer_config) == asn:
+ raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\
+ 'the same as remote-as, this is not allowed!')
+
# ttl-security and ebgp-multihop can't be used in the same configration
if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config:
raise ConfigError('You can not set both ebgp-multihop and ttl-security hops')
- # Check if neighbor has both override capability and strict capability match configured at the same time.
+ # Check if neighbor has both override capability and strict capability match
+ # configured at the same time.
if 'override_capability' in peer_config and 'strict_capability_match' in peer_config:
- raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and strict-capability-match configured at the same time!')
+ raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and '\
+ 'strict-capability-match configured at the same time!')
# Check spaces in the password
if 'password' in peer_config and ' ' in peer_config['password']:
@@ -157,6 +164,12 @@ def verify(bgp):
if not verify_remote_as(peer_config, bgp):
raise ConfigError(f'Neighbor "{peer}" remote-as must be set!')
+ # Peer-group member cannot override remote-as of peer-group
+ if 'peer_group' in peer_config:
+ peer_group = peer_config['peer_group']
+ if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+
# Only checks for ipv4 and ipv6 neighbors
# Check if neighbor address is assigned as system interface address
vrf = None
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 87432bc1c..58e202928 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -22,6 +22,7 @@ from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import get_dhcp_interfaces
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_vrf
from vyos.template import render_to_string
@@ -59,7 +60,9 @@ def get_config(config=None):
# T3680 - get a list of all interfaces currently configured to use DHCP
tmp = get_dhcp_interfaces(conf, vrf)
- if tmp: static['dhcp'] = tmp
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf, vrf)
+ if tmp: static.update({'pppoe' : tmp})
return static
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index 841bf6a39..00b889a11 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 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
@@ -16,14 +16,18 @@
import os
-from copy import deepcopy
from socket import gethostname
from sys import exit
from urllib3 import PoolManager
+from vyos.base import Warning
from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_interface_exists
from vyos.template import render
-from vyos.util import call, chown
+from vyos.util import call
+from vyos.util import chown
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -32,20 +36,10 @@ airbag.enable()
config_file = r'/etc/salt/minion'
master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub'
-default_config_data = {
- 'hash': 'sha256',
- 'log_level': 'warning',
- 'master' : 'salt',
- 'user': 'minion',
- 'group': 'vyattacfg',
- 'salt_id': gethostname(),
- 'mine_interval': '60',
- 'verify_master_pubkey_sign': 'false',
- 'master_key': ''
-}
+user='minion'
+group='vyattacfg'
def get_config(config=None):
- salt = deepcopy(default_config_data)
if config:
conf = config
else:
@@ -54,44 +48,44 @@ def get_config(config=None):
if not conf.exists(base):
return None
- else:
- conf.set_level(base)
- if conf.exists(['hash']):
- salt['hash'] = conf.return_value(['hash'])
+ salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # ID default is dynamic thus we can not use defaults()
+ if 'id' not in salt:
+ salt['id'] = gethostname()
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ salt = dict_merge(default_values, salt)
- if conf.exists(['master']):
- salt['master'] = conf.return_values(['master'])
-
- if conf.exists(['id']):
- salt['salt_id'] = conf.return_value(['id'])
+ if not conf.exists(base):
+ return None
+ else:
+ conf.set_level(base)
- if conf.exists(['user']):
- salt['user'] = conf.return_value(['user'])
+ return salt
- if conf.exists(['interval']):
- salt['interval'] = conf.return_value(['interval'])
+def verify(salt):
+ if not salt:
+ return None
- if conf.exists(['master-key']):
- salt['master_key'] = conf.return_value(['master-key'])
- salt['verify_master_pubkey_sign'] = 'true'
+ if 'hash' in salt and salt['hash'] == 'sha1':
+ Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!')
- return salt
+ if 'source_interface' in salt:
+ verify_interface_exists(salt['source_interface'])
-def verify(salt):
return None
def generate(salt):
if not salt:
return None
- render(config_file, 'salt-minion/minion.tmpl', salt,
- user=salt['user'], group=salt['group'])
+ render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group)
if not os.path.exists(master_keyfile):
- if salt['master_key']:
+ if 'master_key' in salt:
req = PoolManager().request('GET', salt['master_key'], preload_content=False)
-
with open(master_keyfile, 'wb') as f:
while True:
data = req.read(1024)
@@ -100,18 +94,19 @@ def generate(salt):
f.write(data)
req.release_conn()
- chown(master_keyfile, salt['user'], salt['group'])
+ chown(master_keyfile, user, group)
return None
def apply(salt):
+ service_name = 'salt-minion.service'
if not salt:
# Salt removed from running config
- call('systemctl stop salt-minion.service')
+ call(f'systemctl stop {service_name}')
if os.path.exists(config_file):
os.unlink(config_file)
else:
- call('systemctl restart salt-minion.service')
+ call(f'systemctl restart {service_name}')
return None
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 4fc6a4517..e35bb8a0c 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
@@ -149,7 +150,7 @@ def verify(snmp):
tmp = extension_opt['script']
if not os.path.isfile(tmp):
- print(f'WARNING: script "{tmp}" does not exist!')
+ Warning(f'script "{tmp}" does not exist!')
else:
chmod_755(extension_opt['script'])
@@ -158,7 +159,7 @@ def verify(snmp):
# We only wan't to configure addresses that exist on the system.
# Hint the user if they don't exist
if not is_addr_assigned(address):
- print(f'WARNING: SNMP listen address "{address}" not configured!')
+ Warning(f'SNMP listen address "{address}" not configured!')
if 'trap_target' in snmp:
for trap, trap_config in snmp['trap_target'].items():
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index ef726670c..95050624e 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -22,6 +22,7 @@ from copy import deepcopy
from glob import glob
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
@@ -68,8 +69,8 @@ def verify(tftpd):
for address, address_config in tftpd['listen_address'].items():
if not is_addr_assigned(address):
- print(f'WARNING: TFTP server listen address "{address}" not ' \
- 'assigned to any interface!')
+ Warning(f'TFTP server listen address "{address}" not ' \
+ 'assigned to any interface!')
verify_vrf(address_config)
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 99b82ca2d..dc134fd1f 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -553,13 +553,15 @@ def generate(ipsec):
if not local_prefixes or not remote_prefixes:
continue
- passthrough = []
+ passthrough = None
for local_prefix in local_prefixes:
for remote_prefix in remote_prefixes:
local_net = ipaddress.ip_network(local_prefix)
remote_net = ipaddress.ip_network(remote_prefix)
if local_net.overlaps(remote_net):
+ if passthrough is None:
+ passthrough = []
passthrough.append(local_prefix)
ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough
diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
index bb918a468..fa1917ab1 100755
--- a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
+++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -23,14 +23,9 @@
from sys import argv
from sys import exit
-from syslog import syslog
-from syslog import openlog
-from syslog import LOG_PID
-from syslog import LOG_INFO
-
from vyos.configquery import ConfigTreeQuery
+from vyos.configdict import get_interface_dict
from vyos.ifconfig import PPPoEIf
-from vyos.util import read_file
# When the ppp link comes up, this script is called with the following
# parameters
@@ -45,15 +40,10 @@ if (len(argv) < 7):
exit(1)
interface = argv[6]
-dialer_pid = read_file(f'/var/run/{interface}.pid')
-
-openlog(ident=f'pppd[{dialer_pid}]', facility=LOG_INFO)
-syslog('executing ' + argv[0])
conf = ConfigTreeQuery()
-pppoe = conf.get_config_dict(['interfaces', 'pppoe', argv[6]],
- get_first_key=True, key_mangling=('-', '_'))
-pppoe['ifname'] = argv[6]
+_, pppoe = get_interface_dict(conf.config, ['interfaces', 'pppoe'], interface)
-p = PPPoEIf(pppoe['ifname'])
+# Update the config
+p = PPPoEIf(interface)
p.update(pppoe)
diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26
new file mode 100755
index 000000000..a8936235e
--- /dev/null
+++ b/src/migration-scripts/interfaces/25-to-26
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# T4384: pppoe: replace default-route CLI option with common CLI nodes already
+# present for DHCP
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
+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()
+
+base = ['interfaces', 'pppoe']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ tmp_config = base + [ifname, 'default-route']
+ if config.exists(tmp_config):
+ # Retrieve current config value
+ value = config.return_value(tmp_config)
+ # Delete old Config node
+ config.delete(tmp_config)
+ if value == 'none':
+ config.set(base + [ifname, 'no-default-route'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py
index 29db41e37..0628e6135 100755
--- a/src/op_mode/generate_ovpn_client_file.py
+++ b/src/op_mode/generate_ovpn_client_file.py
@@ -18,6 +18,7 @@ import argparse
import os
from jinja2 import Template
+from textwrap import fill
from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig import Section
@@ -117,8 +118,11 @@ if __name__ == '__main__':
exit(f'OpenVPN certificate key "{key}" does not exist!')
ca = config.value(['pki', 'ca', ca, 'certificate'])
+ ca = fill(ca, width=64)
cert = config.value(['pki', 'certificate', cert, 'certificate'])
+ cert = fill(cert, width=64)
key = config.value(['pki', 'certificate', key, 'private', 'key'])
+ key = fill(key, width=64)
remote_host = config.value(base + [interface, 'local-host'])
ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True)
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index e5014452f..91b25567a 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -22,6 +22,7 @@ import psutil
from logging.handlers import SysLogHandler
from shutil import rmtree
+from vyos.base import Warning
from vyos.util import call
from vyos.util import ask_yes_no
from vyos.util import process_named_running
@@ -163,7 +164,7 @@ if cmd_args.action == 'restart':
if cmd_args.daemon != ['']:
for daemon in cmd_args.daemon:
if not process_named_running(daemon):
- print('WARNING: some of listed daemons are not running!')
+ Warning('some of listed daemons are not running!')
# run command to restart daemon
for daemon in cmd_args.daemon:
diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py
index cd6e8ed43..4b1758eea 100755
--- a/src/op_mode/show_dhcp.py
+++ b/src/op_mode/show_dhcp.py
@@ -26,6 +26,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
+from vyos.base import Warning
from vyos.config import Config
from vyos.util import is_systemd_service_running
@@ -213,7 +214,7 @@ if __name__ == '__main__':
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
if not is_systemd_service_running('isc-dhcp-server.service'):
- print("WARNING: DHCP server is configured but not started. Data may be stale.")
+ Warning('DHCP server is configured but not started. Data may be stale.')
if args.leases:
leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py
index 1f987ff7b..b34b730e6 100755
--- a/src/op_mode/show_dhcpv6.py
+++ b/src/op_mode/show_dhcpv6.py
@@ -26,6 +26,7 @@ from datetime import datetime
from isc_dhcp_leases import Lease, IscDhcpLeases
+from vyos.base import Warning
from vyos.config import Config
from vyos.util import is_systemd_service_running
@@ -203,7 +204,7 @@ if __name__ == '__main__':
# if dhcp server is down, inactive leases may still be shown as active, so warn the user.
if not is_systemd_service_running('isc-dhcp-server6.service'):
- print("WARNING: DHCPv6 server is configured but not started. Data may be stale.")
+ Warning('DHCPv6 server is configured but not started. Data may be stale.')
if args.leases:
leases = get_leases(conf, lease_file, args.state, args.pool, args.sort)
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 40854fa8f..8955e5a59 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -88,7 +88,22 @@ def reset_profile(profile, tunnel):
def debug_peer(peer, tunnel):
if not peer or peer == "all":
- call('sudo /usr/sbin/ipsec statusall')
+ debug_commands = [
+ "sudo ipsec statusall",
+ "sudo swanctl -L",
+ "sudo swanctl -l",
+ "sudo swanctl -P",
+ "sudo ip x sa show",
+ "sudo ip x policy show",
+ "sudo ip tunnel show",
+ "sudo ip address",
+ "sudo ip rule show",
+ "sudo ip route | head -100",
+ "sudo ip route show table 220"
+ ]
+ for debug_cmd in debug_commands:
+ print(f'\n### {debug_cmd} ###')
+ call(debug_cmd)
return
if not tunnel or tunnel == 'all':
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index df9f18d2d..9ae7b1ea9 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -421,12 +421,12 @@ def pdns_rec_control(command):
def make_resolv_conf(state):
logger.info(f"Writing {RESOLV_CONF_FILE}")
- render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.tmpl', state,
+ render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.j2', state,
user='root', group='root')
def make_hosts(state):
logger.info(f"Writing {HOSTS_FILE}")
- render(HOSTS_FILE, 'vyos-hostsd/hosts.tmpl', state,
+ render(HOSTS_FILE, 'vyos-hostsd/hosts.j2', state,
user='root', group='root')
def make_pdns_rec_conf(state):
@@ -437,12 +437,12 @@ def make_pdns_rec_conf(state):
chmod_755(PDNS_REC_RUN_DIR)
render(PDNS_REC_LUA_CONF_FILE,
- 'dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl',
+ 'dns-forwarding/recursor.vyos-hostsd.conf.lua.j2',
state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
logger.info(f"Writing {PDNS_REC_ZONES_FILE}")
render(PDNS_REC_ZONES_FILE,
- 'dns-forwarding/recursor.forward-zones.conf.tmpl',
+ 'dns-forwarding/recursor.forward-zones.conf.j2',
state, user=PDNS_REC_USER, group=PDNS_REC_GROUP)
def set_host_name(state, data):