summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--data/templates/dhcp-client/override.conf.j22
-rw-r--r--data/templates/firewall/nftables-policy.j24
-rw-r--r--data/templates/firewall/nftables.j2311
-rw-r--r--data/templates/frr/bgpd.frr.j23
-rw-r--r--data/templates/openvpn/server.conf.j210
-rw-r--r--data/templates/wifi/hostapd.conf.j212
-rw-r--r--data/templates/wifi/hostapd_accept_station.conf.j27
-rw-r--r--data/templates/wifi/hostapd_deny_station.conf.j27
-rw-r--r--data/templates/zabbix-agent/10-override.conf.j214
-rw-r--r--data/templates/zabbix-agent/zabbix-agent.conf.j277
-rw-r--r--data/vyos-firewall-init.conf4
-rw-r--r--debian/control1
-rwxr-xr-xdebian/rules3
-rw-r--r--debian/vyos-1x.links1
-rw-r--r--debian/vyos-1x.preinst2
-rw-r--r--interface-definitions/container.xml.in1
-rw-r--r--interface-definitions/firewall.xml.in704
-rw-r--r--interface-definitions/include/bgp/afi-export-import.xml.i1
-rw-r--r--interface-definitions/include/bgp/afi-label.xml.i13
-rw-r--r--interface-definitions/include/constraint/interface-name-with-wildcard-and-inverted.xml.i4
-rw-r--r--interface-definitions/include/firewall/action-and-notrack.xml.i41
-rw-r--r--interface-definitions/include/firewall/common-rule-inet.xml.i374
-rw-r--r--interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i331
-rw-r--r--interface-definitions/include/firewall/common-rule-ipv4.xml.i72
-rw-r--r--interface-definitions/include/firewall/common-rule-ipv6.xml.i72
-rw-r--r--interface-definitions/include/firewall/default-action-base-chains.xml.i22
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i252
-rw-r--r--interface-definitions/include/firewall/inbound-interface.xml.i10
-rw-r--r--interface-definitions/include/firewall/ipv4-custom-name.xml.i41
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-forward.xml.i36
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-input.xml.i35
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-output.xml.i35
-rw-r--r--interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i85
-rw-r--r--interface-definitions/include/firewall/ipv6-custom-name.xml.i41
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-forward.xml.i36
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-input.xml.i35
-rw-r--r--interface-definitions/include/firewall/ipv6-hook-output.xml.i35
-rw-r--r--interface-definitions/include/firewall/match-interface.xml.i23
-rw-r--r--interface-definitions/include/firewall/outbound-interface.xml.i10
-rw-r--r--interface-definitions/include/interface/ipv6-accept-dad.xml.i20
-rw-r--r--interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i3
-rw-r--r--interface-definitions/include/interface/ipv6-options.xml.i1
-rw-r--r--interface-definitions/include/interface/mac-multi.xml.i15
-rw-r--r--interface-definitions/include/interface/macsec-key.xml.i15
-rw-r--r--interface-definitions/include/interface/per-client-thread.xml.i8
-rw-r--r--interface-definitions/include/radius-server-key.xml.i8
-rw-r--r--interface-definitions/include/version/firewall-version.xml.i2
-rw-r--r--interface-definitions/interfaces-macsec.xml.in22
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in10
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in2
-rw-r--r--interface-definitions/interfaces-wireless.xml.in44
-rw-r--r--interface-definitions/service-monitoring-zabbix-agent.xml.in193
-rw-r--r--interface-definitions/system-login.xml.in7
-rw-r--r--op-mode-definitions/firewall.xml.in204
-rw-r--r--op-mode-definitions/monitor-log.xml.in6
-rw-r--r--op-mode-definitions/pki.xml.in9
-rw-r--r--op-mode-definitions/show-log.xml.in6
-rw-r--r--python/vyos/component_version.py2
-rw-r--r--python/vyos/config.py102
-rw-r--r--python/vyos/configdict.py1
-rw-r--r--python/vyos/configdiff.py18
-rw-r--r--python/vyos/configtree.py18
-rw-r--r--python/vyos/firewall.py80
-rw-r--r--python/vyos/ifconfig/interface.py66
-rw-r--r--python/vyos/ifconfig/macsec.py22
-rw-r--r--python/vyos/ifconfig/wireguard.py4
-rw-r--r--python/vyos/nat.py11
-rw-r--r--python/vyos/pki.py14
-rw-r--r--python/vyos/template.py7
-rw-r--r--python/vyos/utils/convert.py46
-rw-r--r--python/vyos/utils/network.py22
-rw-r--r--python/vyos/xml_ref/__init__.py28
-rw-r--r--python/vyos/xml_ref/definition.py101
-rw-r--r--smoketest/configs/dialup-router-complex4
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py8
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py493
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py72
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py4
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py37
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py44
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py9
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py2
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py10
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py84
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospfv3.py48
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_zabbix-agent.py89
-rwxr-xr-xsrc/conf_mode/conntrack.py14
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py8
-rwxr-xr-xsrc/conf_mode/container.py73
-rwxr-xr-xsrc/conf_mode/dhcp_relay.py10
-rwxr-xr-xsrc/conf_mode/dhcp_server.py18
-rwxr-xr-xsrc/conf_mode/dhcpv6_relay.py11
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py7
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py63
-rwxr-xr-xsrc/conf_mode/firewall.py258
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py32
-rwxr-xr-xsrc/conf_mode/http-api.py12
-rwxr-xr-xsrc/conf_mode/igmp_proxy.py15
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py22
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py74
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py37
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py57
-rwxr-xr-xsrc/conf_mode/lldp.py33
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py14
-rwxr-xr-xsrc/conf_mode/load-balancing-wan.py51
-rwxr-xr-xsrc/conf_mode/nat.py17
-rwxr-xr-xsrc/conf_mode/nat66.py12
-rwxr-xr-xsrc/conf_mode/pki.py5
-rwxr-xr-xsrc/conf_mode/protocols_babel.py19
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py15
-rwxr-xr-xsrc/conf_mode/protocols_failover.py13
-rwxr-xr-xsrc/conf_mode/protocols_isis.py14
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py73
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py19
-rwxr-xr-xsrc/conf_mode/protocols_rip.py5
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py5
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py5
-rwxr-xr-xsrc/conf_mode/qos.py88
-rwxr-xr-xsrc/conf_mode/salt-minion.py5
-rwxr-xr-xsrc/conf_mode/service_config_sync.py10
-rwxr-xr-xsrc/conf_mode/service_console-server.py8
-rwxr-xr-xsrc/conf_mode/service_ids_fastnetmon.py10
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py5
-rwxr-xr-xsrc/conf_mode/service_monitoring_zabbix-agent.py98
-rwxr-xr-xsrc/conf_mode/service_router-advert.py39
-rwxr-xr-xsrc/conf_mode/service_sla.py10
-rwxr-xr-xsrc/conf_mode/service_upnp.py7
-rwxr-xr-xsrc/conf_mode/service_webproxy.py17
-rwxr-xr-xsrc/conf_mode/snmp.py55
-rwxr-xr-xsrc/conf_mode/ssh.py6
-rwxr-xr-xsrc/conf_mode/system-ip.py9
-rwxr-xr-xsrc/conf_mode/system-ipv6.py10
-rwxr-xr-xsrc/conf_mode/system-login.py30
-rwxr-xr-xsrc/conf_mode/system-logs.py10
-rwxr-xr-xsrc/conf_mode/system-option.py11
-rwxr-xr-xsrc/conf_mode/system-syslog.py42
-rwxr-xr-xsrc/conf_mode/system_console.py10
-rwxr-xr-xsrc/conf_mode/system_sflow.py25
-rwxr-xr-xsrc/conf_mode/tftp_server.py10
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py83
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py86
-rwxr-xr-xsrc/conf_mode/vpp.py20
-rwxr-xr-xsrc/conf_mode/vrf.py21
-rwxr-xr-xsrc/etc/netplug/linkdown.d/dhclient65
-rwxr-xr-xsrc/etc/netplug/linkup.d/dhclient64
-rwxr-xr-xsrc/etc/netplug/linkup.d/vyos-python-helper4
-rwxr-xr-xsrc/etc/netplug/netplug41
-rw-r--r--src/etc/netplug/netplugd.conf4
-rwxr-xr-xsrc/etc/netplug/vyos-netplug-dhcp-client62
-rwxr-xr-xsrc/helpers/vyos-domain-resolver.py10
-rwxr-xr-xsrc/init/vyos-router24
-rwxr-xr-xsrc/migration-scripts/firewall/10-to-11374
-rwxr-xr-xsrc/migration-scripts/firewall/5-to-646
-rwxr-xr-xsrc/op_mode/firewall.py223
-rwxr-xr-xsrc/op_mode/neighbor.py5
-rwxr-xr-xsrc/op_mode/pki.py23
-rwxr-xr-xsrc/op_mode/show_openconnect_otp.py38
-rwxr-xr-xsrc/op_mode/vrf.py21
-rw-r--r--src/tests/test_initial_setup.py6
162 files changed, 4653 insertions, 2895 deletions
diff --git a/Makefile b/Makefile
index fe17ce994..b75a78784 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ interface_definitions: $(config_xml_obj)
$(CURDIR)/scripts/override-default $(BUILD_DIR)/interface-definitions
- $(CURDIR)/python/vyos/xml_ref/generate_cache.py --xml-dir $(BUILD_DIR)/interface-definitions
+ $(CURDIR)/python/vyos/xml_ref/generate_cache.py --xml-dir $(BUILD_DIR)/interface-definitions || exit 1
find $(BUILD_DIR)/interface-definitions -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
diff --git a/data/templates/dhcp-client/override.conf.j2 b/data/templates/dhcp-client/override.conf.j2
index 03fd71bf1..d09320270 100644
--- a/data/templates/dhcp-client/override.conf.j2
+++ b/data/templates/dhcp-client/override.conf.j2
@@ -10,6 +10,6 @@ ConditionPathExists={{ isc_dhclient_dir }}/dhclient_%i.conf
ExecStart=
ExecStart={{ vrf_command }}/sbin/dhclient -4 {{ dhclient_options }} {{ ifname }}
ExecStop=
-ExecStop=/sbin/dhclient -4 -r {{ dhclient_options }} {{ ifname }}
+ExecStop={{ vrf_command }}/sbin/dhclient -4 -r {{ dhclient_options }} {{ ifname }}
WorkingDirectory={{ isc_dhclient_dir }}
PIDFile={{ isc_dhclient_dir }}/dhclient_%i.pid
diff --git a/data/templates/firewall/nftables-policy.j2 b/data/templates/firewall/nftables-policy.j2
index 1c9bda64f..699349e2b 100644
--- a/data/templates/firewall/nftables-policy.j2
+++ b/data/templates/firewall/nftables-policy.j2
@@ -25,7 +25,7 @@ table ip vyos_mangle {
chain VYOS_PBR_UD_{{ route_text }} {
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
- {{ rule_conf | nft_rule(route_text, rule_id, 'ip') }}
+ {{ rule_conf | nft_rule('route', route_text, rule_id, 'ip') }}
{% endfor %}
{% endif %}
}
@@ -54,7 +54,7 @@ table ip6 vyos_mangle {
chain VYOS_PBR6_UD_{{ route_text }} {
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
- {{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }}
+ {{ rule_conf | nft_rule('route6', route_text, rule_id, 'ip6') }}
{% endfor %}
{% endif %}
}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 2c7115134..10cbc68cb 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,78 +1,101 @@
#!/usr/sbin/nft -f
{% import 'firewall/nftables-defines.j2' as group_tmpl %}
-{% import 'firewall/nftables-zone.j2' as zone_tmpl %}
{% if first_install is not vyos_defined %}
delete table ip vyos_filter
{% endif %}
table ip vyos_filter {
- chain VYOS_FW_FORWARD {
- type filter hook forward priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY
-{% endif %}
-{% if interface is vyos_defined %}
-{% for ifname, ifconf in interface.items() %}
-{% if ifconf.in is vyos_defined and ifconf.in.name is vyos_defined %}
- iifname {{ ifname }} counter jump NAME_{{ ifconf.in.name }}
-{% endif %}
-{% if ifconf.out is vyos_defined and ifconf.out.name is vyos_defined %}
- oifname {{ ifname }} counter jump NAME_{{ ifconf.out.name }}
-{% endif %}
-{% endfor %}
-{% endif %}
- jump VYOS_POST_FW
- }
- chain VYOS_FW_LOCAL {
- type filter hook input priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY
-{% endif %}
-{% if interface is vyos_defined %}
-{% for ifname, ifconf in interface.items() %}
-{% if ifconf.local is vyos_defined and ifconf.local.name is vyos_defined %}
- iifname {{ ifname }} counter jump NAME_{{ ifconf.local.name }}
-{% endif %}
-{% endfor %}
-{% endif %}
- jump VYOS_POST_FW
+{% if ipv4 is vyos_defined %}
+{% set ns = namespace(sets=[]) %}
+{% if ipv4.forward is vyos_defined %}
+{% for prior, conf in ipv4.forward.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_FORWARD_{{ prior }} {
+ type filter hook forward priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('FWD', prior, rule_id) }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
- chain VYOS_FW_OUTPUT {
- type filter hook output priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY
-{% endif %}
- jump VYOS_POST_FW
+{% endfor %}
+{% endif %}
+
+{% if ipv4.input is vyos_defined %}
+{% for prior, conf in ipv4.input.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_INPUT_{{ prior }} {
+ type filter hook input priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('INP',prior, rule_id) }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
- chain VYOS_POST_FW {
- return
+{% endfor %}
+{% endif %}
+
+{% if ipv4.output is vyos_defined %}
+{% for prior, conf in ipv4.output.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_OUTPUT_{{ prior }} {
+ type filter hook output priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('OUT', prior, rule_id) }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
+{% endfor %}
+{% endif %}
chain VYOS_FRAG_MARK {
type filter hook prerouting priority -450; policy accept;
ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return
}
-{% if name is vyos_defined %}
-{% set ns = namespace(sets=[]) %}
-{% for name_text, conf in name.items() %}
+{% if ipv4.prerouting is vyos_defined %}
+{% for prior, conf in ipv4.prerouting.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_PREROUTING_{{ prior }} {
+ type filter hook prerouting priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('PRE', prior, rule_id) }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['PRE_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(prior) }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if ipv4.name is vyos_defined %}
+{% for name_text, conf in ipv4.name.items() %}
chain NAME_{{ name_text }} {
-{% if conf.rule is vyos_defined %}
-{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
- {{ rule_conf | nft_rule(name_text, rule_id) }}
-{% if rule_conf.recent is vyos_defined %}
-{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
-{% endif %}
-{% endfor %}
-{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('NAM', name_text, rule_id) }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
{{ conf | nft_default_rule(name_text) }}
}
-{% endfor %}
-{% for set_name in ip_fqdn %}
- set FQDN_{{ set_name }} {
- type ipv4_addr
- flags interval
- }
-{% endfor %}
+{% endfor %}
+{% endif %}
+
{% for set_name in ns.sets %}
set RECENT_{{ set_name }} {
type ipv4_addr
@@ -80,6 +103,12 @@ table ip vyos_filter {
flags dynamic
}
{% endfor %}
+{% for set_name in ip_fqdn %}
+ set FQDN_{{ set_name }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
{% if geoip_updated.name is vyos_defined %}
{% for setname in geoip_updated.name %}
set {{ setname }} {
@@ -89,99 +118,87 @@ table ip vyos_filter {
{% endfor %}
{% endif %}
{% endif %}
-
{{ group_tmpl.groups(group, False) }}
-
-{% if zone is vyos_defined %}
-{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, False) }}
-{% endif %}
-
-{% if state_policy is vyos_defined %}
- chain VYOS_STATE_POLICY {
-{% if state_policy.established is vyos_defined %}
- {{ state_policy.established | nft_state_policy('established') }}
-{% endif %}
-{% if state_policy.invalid is vyos_defined %}
- {{ state_policy.invalid | nft_state_policy('invalid') }}
-{% endif %}
-{% if state_policy.related is vyos_defined %}
- {{ state_policy.related | nft_state_policy('related') }}
-{% endif %}
- return
- }
-{% endif %}
}
{% if first_install is not vyos_defined %}
delete table ip6 vyos_filter
{% endif %}
table ip6 vyos_filter {
- chain VYOS_FW6_FORWARD {
- type filter hook forward priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY6
-{% endif %}
-{% if interface is vyos_defined %}
-{% for ifname, ifconf in interface.items() %}
-{% if ifconf.in is vyos_defined and ifconf.in.ipv6_name is vyos_defined %}
- iifname {{ ifname }} counter jump NAME6_{{ ifconf.in.ipv6_name }}
-{% endif %}
-{% if ifconf.out is vyos_defined and ifconf.out.ipv6_name is vyos_defined %}
- oifname {{ ifname }} counter jump NAME6_{{ ifconf.out.ipv6_name }}
-{% endif %}
-{% endfor %}
-{% endif %}
- jump VYOS_POST_FW6
- }
- chain VYOS_FW6_LOCAL {
- type filter hook input priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY6
-{% endif %}
-{% if interface is vyos_defined %}
-{% for ifname, ifconf in interface.items() %}
-{% if ifconf.local is vyos_defined and ifconf.local.ipv6_name is vyos_defined %}
- iifname {{ ifname }} counter jump NAME6_{{ ifconf.local.ipv6_name }}
-{% endif %}
-{% endfor %}
-{% endif %}
- jump VYOS_POST_FW6
+{% if ipv6 is vyos_defined %}
+{% set ns = namespace(sets=[]) %}
+{% if ipv6.forward is vyos_defined %}
+{% for prior, conf in ipv6.forward.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_IPV6_FORWARD_{{ prior }} {
+ type filter hook forward priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('FWD', prior, rule_id ,'ip6') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
- chain VYOS_FW6_OUTPUT {
- type filter hook output priority 0; policy accept;
-{% if state_policy is vyos_defined %}
- jump VYOS_STATE_POLICY6
-{% endif %}
- jump VYOS_POST_FW6
+{% endfor %}
+{% endif %}
+
+{% if ipv6.input is vyos_defined %}
+{% for prior, conf in ipv6.input.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_IPV6_INPUT_{{ prior }} {
+ type filter hook input priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('INP', prior, rule_id ,'ip6') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
- chain VYOS_POST_FW6 {
- return
+{% endfor %}
+{% endif %}
+
+{% if ipv6.output is vyos_defined %}
+{% for prior, conf in ipv6.output.items() %}
+{% set def_action = conf.default_action %}
+ chain VYOS_IPV6_OUTPUT_{{ prior }} {
+ type filter hook output priority {{ prior }}; policy {{ def_action }};
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('OUT', prior, rule_id ,'ip6') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['OUT_ ' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
+{% endfor %}
+{% endif %}
+
chain VYOS_FRAG6_MARK {
type filter hook prerouting priority -450; policy accept;
exthdr frag exists meta mark set 0xffff1 return
}
-{% if ipv6_name is vyos_defined %}
-{% set ns = namespace(sets=[]) %}
-{% for name_text, conf in ipv6_name.items() %}
+
+{% if ipv6.name is vyos_defined %}
+{% for name_text, conf in ipv6.name.items() %}
chain NAME6_{{ name_text }} {
-{% if conf.rule is vyos_defined %}
-{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
- {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }}
-{% if rule_conf.recent is vyos_defined %}
-{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
-{% endif %}
-{% endfor %}
-{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'ip6') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
{{ conf | nft_default_rule(name_text, ipv6=True) }}
}
-{% endfor %}
-{% for set_name in ip6_fqdn %}
- set FQDN_{{ set_name }} {
- type ipv6_addr
- flags interval
- }
-{% endfor %}
+{% endfor %}
+{% endif %}
+
{% for set_name in ns.sets %}
set RECENT6_{{ set_name }} {
type ipv6_addr
@@ -189,6 +206,12 @@ table ip6 vyos_filter {
flags dynamic
}
{% endfor %}
+{% for set_name in ip6_fqdn %}
+ set FQDN_{{ set_name }} {
+ type ipv6_addr
+ flags interval
+ }
+{% endfor %}
{% if geoip_updated.ipv6_name is vyos_defined %}
{% for setname in geoip_updated.ipv6_name %}
set {{ setname }} {
@@ -198,25 +221,5 @@ table ip6 vyos_filter {
{% endfor %}
{% endif %}
{% endif %}
-
{{ group_tmpl.groups(group, True) }}
-
-{% if zone is vyos_defined %}
-{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, True) }}
-{% endif %}
-
-{% if state_policy is vyos_defined %}
- chain VYOS_STATE_POLICY6 {
-{% if state_policy.established is vyos_defined %}
- {{ state_policy.established | nft_state_policy('established') }}
-{% endif %}
-{% if state_policy.invalid is vyos_defined %}
- {{ state_policy.invalid | nft_state_policy('invalid') }}
-{% endif %}
-{% if state_policy.related is vyos_defined %}
- {{ state_policy.related | nft_state_policy('related') }}
-{% endif %}
- return
- }
-{% endif %}
-}
+} \ No newline at end of file
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 4535758da..7fa974254 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -349,6 +349,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if afi_config.label.vpn.export is vyos_defined %}
label vpn export {{ afi_config.label.vpn.export }}
{% endif %}
+{% if afi_config.label.vpn.allocation_mode.per_nexthop is vyos_defined %}
+ label vpn export allocation-mode per-nexthop
+{% endif %}
{% if afi_config.local_install is vyos_defined %}
{% for interface in afi_config.local_install.interface %}
local-install {{ interface }}
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index d144529f3..f76fbbe79 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -185,7 +185,7 @@ tls-version-min {{ tls.tls_version_min }}
{% 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 %}
+{% else %}
dh none
{% endif %}
{% if tls.auth_key is vyos_defined %}
@@ -200,6 +200,14 @@ tls-client
{% elif tls.role is vyos_defined('passive') %}
tls-server
{% endif %}
+
+{% if tls.peer_fingerprint is vyos_defined %}
+<peer-fingerprint>
+{% for fp in tls.peer_fingerprint %}
+{{ fp }}
+{% endfor %}
+</peer-fingerprint>
+{% endif %}
{% endif %}
# Encryption options
diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2
index f2312d2d4..613038597 100644
--- a/data/templates/wifi/hostapd.conf.j2
+++ b/data/templates/wifi/hostapd.conf.j2
@@ -430,14 +430,22 @@ ieee80211n={{ '1' if 'n' in mode or 'ac' in mode else '0' }}
ignore_broadcast_ssid=1
{% endif %}
-# Station MAC address -based authentication
+{% if type is vyos_defined('access-point') %}
+# Station MAC address-based authentication
# Please note that this kind of access control requires a driver that uses
# hostapd to take care of management frame processing and as such, this can be
# used with driver=hostap or driver=nl80211, but not with driver=atheros.
# 0 = accept unless in deny list
# 1 = deny unless in accept list
# 2 = use external RADIUS server (accept/deny lists are searched first)
-macaddr_acl=0
+macaddr_acl={{ '0' if security.station_address.mode is vyos_defined('accept') else '1' }}
+
+# Accept/deny lists are read from separate files (containing list of
+# MAC addresses, one per line). Use absolute path name to make sure that the
+# files can be read on SIGHUP configuration reloads.
+accept_mac_file={{ hostapd_accept_station_conf }}
+deny_mac_file={{ hostapd_deny_station_conf }}
+{% endif %}
{% if max_stations is vyos_defined %}
# Maximum number of stations allowed in station table. New stations will be
diff --git a/data/templates/wifi/hostapd_accept_station.conf.j2 b/data/templates/wifi/hostapd_accept_station.conf.j2
new file mode 100644
index 000000000..a381c947c
--- /dev/null
+++ b/data/templates/wifi/hostapd_accept_station.conf.j2
@@ -0,0 +1,7 @@
+# List of MAC addresses that are allowed to authenticate (IEEE 802.11)
+# with the AP
+{% if security.station_address.accept.mac is vyos_defined %}
+{% for mac in security.station_address.accept.mac %}
+{{ mac | lower }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/wifi/hostapd_deny_station.conf.j2 b/data/templates/wifi/hostapd_deny_station.conf.j2
new file mode 100644
index 000000000..fb2950dda
--- /dev/null
+++ b/data/templates/wifi/hostapd_deny_station.conf.j2
@@ -0,0 +1,7 @@
+# List of MAC addresses that are not allowed to authenticate
+# (IEEE 802.11) with the access point
+{% if security.station_address.deny.mac is vyos_defined %}
+{% for mac in security.station_address.deny.mac %}
+{{ mac | lower }}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/zabbix-agent/10-override.conf.j2 b/data/templates/zabbix-agent/10-override.conf.j2
new file mode 100644
index 000000000..7c296e8fd
--- /dev/null
+++ b/data/templates/zabbix-agent/10-override.conf.j2
@@ -0,0 +1,14 @@
+[Unit]
+After=
+After=vyos-router.service
+ConditionPathExists=
+ConditionPathExists=/run/zabbix/zabbix-agent2.conf
+
+[Service]
+EnvironmentFile=
+ExecStart=
+ExecStart=/usr/sbin/zabbix_agent2 --config /run/zabbix/zabbix-agent2.conf --foreground
+WorkingDirectory=
+WorkingDirectory=/run/zabbix
+Restart=always
+RestartSec=10
diff --git a/data/templates/zabbix-agent/zabbix-agent.conf.j2 b/data/templates/zabbix-agent/zabbix-agent.conf.j2
new file mode 100644
index 000000000..e6dcef872
--- /dev/null
+++ b/data/templates/zabbix-agent/zabbix-agent.conf.j2
@@ -0,0 +1,77 @@
+# Generated by ${vyos_conf_scripts_dir}/service_monitoring_zabbix-agent.py
+
+PidFile=/run/zabbix/zabbix_agent2.pid
+LogFile=/var/log/zabbix/zabbix_agent2.log
+ControlSocket=/run/zabbix/agent.sock
+
+{% if log is vyos_defined %}
+{% if log.size is vyos_defined %}
+### Option: LogFileSize
+# Maximum size of log file in MB.
+# 0 - disable automatic log rotation.
+#
+# Range: 0-1024
+LogFileSize={{ log.size }}
+{% endif %}
+{% if log.remote_commands is vyos_defined %}
+LogRemoteCommands=1
+{% endif %}
+{% if log.debug_level is vyos_defined %}
+{% set mapping = {
+ 'basic': 0,
+ 'critical': 1,
+ 'error': 2,
+ 'warning': 3,
+ 'debug': 4,
+ 'extended-debug': 5
+ } %}
+DebugLevel={{ mapping[log.debug_level] }}
+{% endif %}
+{% endif %}
+
+{% if server is vyos_defined %}
+Server={{ server | bracketize_ipv6 | join(',') }}
+{% endif %}
+{% if server_active is vyos_defined %}
+{% set servers = [] %}
+{% for key, value in server_active.items() %}
+{% if value.port %}
+{% set serv_item = key | bracketize_ipv6 + ':' + value.port %}
+{% set _ = servers.append(serv_item) %}
+{% else %}
+{% set _ = servers.append(key | bracketize_ipv6) %}
+{% endif %}
+{% endfor %}
+ServerActive={{ servers | join(',') }}
+{% endif %}
+
+{% if host_name is vyos_defined %}
+Hostname={{ host_name }}
+{% endif %}
+
+{% if port is vyos_defined %}
+ListenPort={{ port }}
+{% endif %}
+{% if listen_address is vyos_defined %}
+ListenIP={{ listen_address | join(',') }}
+{% endif %}
+
+{% if limits is vyos_defined %}
+{% if limits.buffer_flush_interval is vyos_defined %}
+BufferSend={{ limits.buffer_flush_interval }}
+{% endif %}
+{% if limits.buffer_size is vyos_defined %}
+BufferSize={{ limits.buffer_size }}
+{% endif %}
+{% endif %}
+
+{% if directory is vyos_defined %}
+### Option: Include
+# You may include individual files or all files in a directory in the configuration file.
+Include={{ directory }}/*.conf
+{% endif %}
+
+{% if timeout is vyos_defined %}
+Timeout={{ timeout }}
+{% endif %}
+
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 11a5bc7bf..36d92fe93 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -20,7 +20,7 @@ table raw {
}
chain PREROUTING {
- type filter hook prerouting priority -200; policy accept;
+ type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_PREROUTING_HOOK
@@ -29,7 +29,7 @@ table raw {
}
chain OUTPUT {
- type filter hook output priority -200; policy accept;
+ type filter hook output priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_OUTPUT_HOOK
diff --git a/debian/control b/debian/control
index 772edb540..ee45a5fe3 100644
--- a/debian/control
+++ b/debian/control
@@ -191,6 +191,7 @@ Depends:
wireguard-tools,
wireless-regdb,
wpasupplicant (>= 0.6.7),
+ zabbix-agent2,
ndppd,
miniupnpd-nftables
Description: VyOS configuration scripts and data
diff --git a/debian/rules b/debian/rules
index 9ada2bf87..e6bbeeafb 100755
--- a/debian/rules
+++ b/debian/rules
@@ -32,9 +32,6 @@ override_dh_auto_build:
override_dh_auto_install:
dh_auto_install
- # convert the XML to dictionaries
- env PYTHONPATH=python python3 python/vyos/xml/generate.py
-
cd python; python3 setup.py install --install-layout=deb --root ../$(DIR); cd ..
# Install scripts
diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links
new file mode 100644
index 000000000..0e2d1b841
--- /dev/null
+++ b/debian/vyos-1x.links
@@ -0,0 +1 @@
+/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper
diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst
index 92037a915..e355ffa84 100644
--- a/debian/vyos-1x.preinst
+++ b/debian/vyos-1x.preinst
@@ -8,3 +8,5 @@ dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile
dpkg-divert --package vyos-1x --add --no-rename /etc/sysctl.d/80-vpp.conf
+dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf
+dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 6d2eb18d0..baab6104f 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -213,6 +213,7 @@
<help>Publish port to the container</help>
</properties>
<children>
+ #include <include/listen-address.xml.i>
<leafNode name="source">
<properties>
<help>Source host port</help>
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 1cdc7b819..127f4b7e7 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -6,66 +6,7 @@
<help>Firewall</help>
</properties>
<children>
- <leafNode name="all-ping">
- <properties>
- <help>Policy for handling of all IPv4 ICMP echo requests</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of all IPv4 ICMP echo requests</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of all IPv4 ICMP echo requests</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>enable</defaultValue>
- </leafNode>
- <leafNode name="broadcast-ping">
- <properties>
- <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <leafNode name="config-trap">
- <properties>
- <help>SNMP trap generation on firewall configuration changes</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable sending SNMP trap on firewall configuration change</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable sending SNMP trap on firewall configuration change</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
+ #include <include/firewall/global-options.xml.i>
<node name="group">
<properties>
<help>Firewall group</help>
@@ -343,645 +284,28 @@
</tagNode>
</children>
</node>
- <tagNode name="interface">
+ <node name="ipv4">
<properties>
- <help>Interface name to apply firewall configuration</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <constraint>
- #include <include/constraint/interface-name-with-wildcard.xml.i>
- </constraint>
+ <help>IPv4 firewall</help>
</properties>
<children>
- <node name="in">
- <properties>
- <help>Forwarded packets on inbound interface</help>
- </properties>
- <children>
- #include <include/firewall/name.xml.i>
- </children>
- </node>
- <node name="out">
- <properties>
- <help>Forwarded packets on outbound interface</help>
- </properties>
- <children>
- #include <include/firewall/name.xml.i>
- </children>
- </node>
- <node name="local">
- <properties>
- <help>Packets destined for this router</help>
- </properties>
- <children>
- #include <include/firewall/name.xml.i>
- </children>
- </node>
- </children>
- </tagNode>
- <leafNode name="ip-src-route">
- <properties>
- <help>Policy for handling IPv4 packets with source route option</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of IPv4 packets with source route option</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of IPv4 packets with source route option</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <tagNode name="ipv6-name">
- <properties>
- <help>IPv6 firewall rule-set name</help>
- <constraint>
- <regex>[a-zA-Z0-9][\w\-\.]*</regex>
- </constraint>
- </properties>
- <children>
- #include <include/firewall/default-action.xml.i>
- #include <include/firewall/enable-default-log.xml.i>
- #include <include/generic-description.xml.i>
- <leafNode name="default-jump-target">
- <properties>
- <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <tagNode name="rule">
- <properties>
- <help>Firewall rule number (IPv6)</help>
- <valueHelp>
- <format>u32:1-999999</format>
- <description>Number for this Firewall rule</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-999999"/>
- </constraint>
- <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
- </properties>
- <children>
- #include <include/firewall/action.xml.i>
- #include <include/generic-description.xml.i>
- <node name="destination">
- <properties>
- <help>Destination parameters</help>
- </properties>
- <children>
- #include <include/firewall/address-ipv6.xml.i>
- #include <include/firewall/fqdn.xml.i>
- #include <include/firewall/geoip.xml.i>
- #include <include/firewall/source-destination-group-ipv6.xml.i>
- #include <include/firewall/port.xml.i>
- #include <include/firewall/address-mask-ipv6.xml.i>
- </children>
- </node>
- <node name="source">
- <properties>
- <help>Source parameters</help>
- </properties>
- <children>
- #include <include/firewall/address-ipv6.xml.i>
- #include <include/firewall/fqdn.xml.i>
- #include <include/firewall/geoip.xml.i>
- #include <include/firewall/source-destination-group-ipv6.xml.i>
- #include <include/firewall/port.xml.i>
- #include <include/firewall/address-mask-ipv6.xml.i>
- </children>
- </node>
- #include <include/firewall/common-rule.xml.i>
- #include <include/firewall/dscp.xml.i>
- #include <include/firewall/packet-options.xml.i>
- #include <include/firewall/hop-limit.xml.i>
- #include <include/firewall/connection-mark.xml.i>
- <node name="icmpv6">
- <properties>
- <help>ICMPv6 type and code information</help>
- </properties>
- <children>
- <leafNode name="code">
- <properties>
- <help>ICMPv6 code</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMPv6 code (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="type">
- <properties>
- <help>ICMPv6 type</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMPv6 type (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/firewall/icmpv6-type-name.xml.i>
- </children>
- </node>
- <leafNode name="jump-target">
- <properties>
- <help>Set jump target. Action jump must be defined to use this setting</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- #include <include/firewall/nft-queue.xml.i>
- </children>
- </tagNode>
- </children>
- </tagNode>
- <leafNode name="ipv6-receive-redirects">
- <properties>
- <help>Policy for handling received ICMPv6 redirect messages</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of received ICMPv6 redirect messages</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of received ICMPv6 redirect messages</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <leafNode name="ipv6-src-route">
- <properties>
- <help>Policy for handling IPv6 packets with routing extension header</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of IPv6 packets with routing header type 2</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of IPv6 packets with routing header</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <leafNode name="log-martians">
- <properties>
- <help>Policy for logging IPv4 packets with invalid addresses</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable logging of IPv4 packets with invalid addresses</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable logging of Ipv4 packets with invalid addresses</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>enable</defaultValue>
- </leafNode>
- <tagNode name="name">
- <properties>
- <help>IPv4 firewall rule-set name</help>
- <constraint>
- <regex>[a-zA-Z0-9][\w\-\.]*</regex>
- </constraint>
- </properties>
- <children>
- #include <include/firewall/default-action.xml.i>
- #include <include/firewall/enable-default-log.xml.i>
- #include <include/generic-description.xml.i>
- <leafNode name="default-jump-target">
- <properties>
- <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <tagNode name="rule">
- <properties>
- <help>Firewall rule number (IPv4)</help>
- <valueHelp>
- <format>u32:1-999999</format>
- <description>Number for this Firewall rule</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-999999"/>
- </constraint>
- <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
- </properties>
- <children>
- #include <include/firewall/action.xml.i>
- #include <include/generic-description.xml.i>
- <node name="destination">
- <properties>
- <help>Destination parameters</help>
- </properties>
- <children>
- #include <include/firewall/address.xml.i>
- #include <include/firewall/fqdn.xml.i>
- #include <include/firewall/geoip.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
- #include <include/firewall/port.xml.i>
- #include <include/firewall/address-mask.xml.i>
- </children>
- </node>
- <node name="source">
- <properties>
- <help>Source parameters</help>
- </properties>
- <children>
- #include <include/firewall/address.xml.i>
- #include <include/firewall/fqdn.xml.i>
- #include <include/firewall/geoip.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
- #include <include/firewall/port.xml.i>
- #include <include/firewall/address-mask.xml.i>
- </children>
- </node>
- #include <include/firewall/common-rule.xml.i>
- #include <include/firewall/dscp.xml.i>
- #include <include/firewall/packet-options.xml.i>
- #include <include/firewall/connection-mark.xml.i>
- <node name="icmp">
- <properties>
- <help>ICMP type and code information</help>
- </properties>
- <children>
- <leafNode name="code">
- <properties>
- <help>ICMP code</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMP code (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="type">
- <properties>
- <help>ICMP type</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>ICMP type (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/firewall/icmp-type-name.xml.i>
- </children>
- </node>
- <leafNode name="jump-target">
- <properties>
- <help>Set jump target. Action jump must be defined to use this setting</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- #include <include/firewall/ttl.xml.i>
- #include <include/firewall/nft-queue.xml.i>
- </children>
- </tagNode>
- </children>
- </tagNode>
- <leafNode name="receive-redirects">
- <properties>
- <help>Policy for handling received IPv4 ICMP redirect messages</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable processing of received IPv4 ICMP redirect messages</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable processing of received IPv4 ICMP redirect messages</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <leafNode name="resolver-cache">
- <properties>
- <help>Retains last successful value if domain resolution fails</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="resolver-interval">
- <properties>
- <help>Domain resolver update interval</help>
- <valueHelp>
- <format>u32:10-3600</format>
- <description>Interval (seconds)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 10-3600"/>
- </constraint>
- </properties>
- <defaultValue>300</defaultValue>
- </leafNode>
- <leafNode name="send-redirects">
- <properties>
- <help>Policy for sending IPv4 ICMP redirect messages</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable sending IPv4 ICMP redirect messages</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable sending IPv4 ICMP redirect messages</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>enable</defaultValue>
- </leafNode>
- <leafNode name="source-validation">
- <properties>
- <help>Policy for source validation by reversed path, as specified in RFC3704</help>
- <completionHelp>
- <list>strict loose disable</list>
- </completionHelp>
- <valueHelp>
- <format>strict</format>
- <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description>
- </valueHelp>
- <valueHelp>
- <format>loose</format>
- <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>No source validation</description>
- </valueHelp>
- <constraint>
- <regex>(strict|loose|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <node name="state-policy">
- <properties>
- <help>Global firewall state-policy</help>
- </properties>
- <children>
- <node name="established">
- <properties>
- <help>Global firewall policy for packets part of an established connection</help>
- </properties>
- <children>
- #include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
- #include <include/firewall/rule-log-level.xml.i>
- </children>
- </node>
- <node name="invalid">
- <properties>
- <help>Global firewall policy for packets part of an invalid connection</help>
- </properties>
- <children>
- #include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
- #include <include/firewall/rule-log-level.xml.i>
- </children>
- </node>
- <node name="related">
- <properties>
- <help>Global firewall policy for packets part of a related connection</help>
- </properties>
- <children>
- #include <include/firewall/action-accept-drop-reject.xml.i>
- #include <include/firewall/log.xml.i>
- #include <include/firewall/rule-log-level.xml.i>
- </children>
- </node>
+ #include <include/firewall/ipv4-hook-forward.xml.i>
+ #include <include/firewall/ipv4-hook-input.xml.i>
+ #include <include/firewall/ipv4-hook-output.xml.i>
+ #include <include/firewall/ipv4-custom-name.xml.i>
</children>
</node>
- <leafNode name="syn-cookies">
- <properties>
- <help>Policy for using TCP SYN cookies with IPv4</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable use of TCP SYN cookies with IPv4</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable use of TCP SYN cookies with IPv4</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>enable</defaultValue>
- </leafNode>
- <leafNode name="twa-hazards-protection">
+ <node name="ipv6">
<properties>
- <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help>
- <completionHelp>
- <list>enable disable</list>
- </completionHelp>
- <valueHelp>
- <format>enable</format>
- <description>Enable RFC1337 TIME-WAIT hazards protection</description>
- </valueHelp>
- <valueHelp>
- <format>disable</format>
- <description>Disable RFC1337 TIME-WAIT hazards protection</description>
- </valueHelp>
- <constraint>
- <regex>(enable|disable)</regex>
- </constraint>
- </properties>
- <defaultValue>disable</defaultValue>
- </leafNode>
- <tagNode name="zone">
- <properties>
- <help>Zone-policy</help>
- <valueHelp>
- <format>txt</format>
- <description>Zone name</description>
- </valueHelp>
- <constraint>
- <regex>[a-zA-Z0-9][\w\-\.]*</regex>
- </constraint>
+ <help>IPv6 firewall</help>
</properties>
<children>
- #include <include/generic-description.xml.i>
- #include <include/firewall/enable-default-log.xml.i>
- <leafNode name="default-action">
- <properties>
- <help>Default-action for traffic coming into this zone</help>
- <completionHelp>
- <list>drop reject</list>
- </completionHelp>
- <valueHelp>
- <format>drop</format>
- <description>Drop silently</description>
- </valueHelp>
- <valueHelp>
- <format>reject</format>
- <description>Drop and notify source</description>
- </valueHelp>
- <constraint>
- <regex>(drop|reject)</regex>
- </constraint>
- </properties>
- <defaultValue>drop</defaultValue>
- </leafNode>
- <tagNode name="from">
- <properties>
- <help>Zone from which to filter traffic</help>
- <completionHelp>
- <path>zone-policy zone</path>
- </completionHelp>
- </properties>
- <children>
- <node name="firewall">
- <properties>
- <help>Firewall options</help>
- </properties>
- <children>
- <leafNode name="ipv6-name">
- <properties>
- <help>IPv6 firewall ruleset</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="name">
- <properties>
- <help>IPv4 firewall ruleset</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </tagNode>
- <leafNode name="interface">
- <properties>
- <help>Interface associated with zone</help>
- <valueHelp>
- <format>txt</format>
- <description>Interface associated with zone</description>
- </valueHelp>
- <valueHelp>
- <format>vrf</format>
- <description>VRF associated with zone</description>
- </valueHelp>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- <path>vrf name</path>
- </completionHelp>
- <multi/>
- </properties>
- </leafNode>
- <node name="intra-zone-filtering">
- <properties>
- <help>Intra-zone filtering</help>
- </properties>
- <children>
- <leafNode name="action">
- <properties>
- <help>Action for intra-zone traffic</help>
- <completionHelp>
- <list>accept drop</list>
- </completionHelp>
- <valueHelp>
- <format>accept</format>
- <description>Accept traffic</description>
- </valueHelp>
- <valueHelp>
- <format>drop</format>
- <description>Drop silently</description>
- </valueHelp>
- <constraint>
- <regex>(accept|drop)</regex>
- </constraint>
- </properties>
- </leafNode>
- <node name="firewall">
- <properties>
- <help>Use the specified firewall chain</help>
- </properties>
- <children>
- <leafNode name="ipv6-name">
- <properties>
- <help>IPv6 firewall ruleset</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="name">
- <properties>
- <help>IPv4 firewall ruleset</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </node>
- <leafNode name="local-zone">
- <properties>
- <help>Zone to be local-zone</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/firewall/ipv6-hook-forward.xml.i>
+ #include <include/firewall/ipv6-hook-input.xml.i>
+ #include <include/firewall/ipv6-hook-output.xml.i>
+ #include <include/firewall/ipv6-custom-name.xml.i>
</children>
- </tagNode>
+ </node>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/include/bgp/afi-export-import.xml.i b/interface-definitions/include/bgp/afi-export-import.xml.i
index 86817cdb3..5223af0ae 100644
--- a/interface-definitions/include/bgp/afi-export-import.xml.i
+++ b/interface-definitions/include/bgp/afi-export-import.xml.i
@@ -32,6 +32,7 @@
</valueHelp>
<completionHelp>
<path>vrf name</path>
+ <list>default</list>
</completionHelp>
<multi/>
</properties>
diff --git a/interface-definitions/include/bgp/afi-label.xml.i b/interface-definitions/include/bgp/afi-label.xml.i
index 9535d19e8..2c5eed18b 100644
--- a/interface-definitions/include/bgp/afi-label.xml.i
+++ b/interface-definitions/include/bgp/afi-label.xml.i
@@ -29,6 +29,19 @@
</constraint>
</properties>
</leafNode>
+ <node name="allocation-mode">
+ <properties>
+ <help>Label allocation mode</help>
+ </properties>
+ <children>
+ <leafNode name="per-nexthop">
+ <properties>
+ <help>Allocate a label per connected next-hop in the VRF</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/interface-definitions/include/constraint/interface-name-with-wildcard-and-inverted.xml.i b/interface-definitions/include/constraint/interface-name-with-wildcard-and-inverted.xml.i
new file mode 100644
index 000000000..6a39041a3
--- /dev/null
+++ b/interface-definitions/include/constraint/interface-name-with-wildcard-and-inverted.xml.i
@@ -0,0 +1,4 @@
+<!-- include start from constraint/interface-name-with-wildcard-and-inverted.xml.i -->
+<regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex>
+<validator name="file-path --lookup-path /sys/class/net --directory"/>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/action-and-notrack.xml.i b/interface-definitions/include/firewall/action-and-notrack.xml.i
new file mode 100644
index 000000000..5f81a1451
--- /dev/null
+++ b/interface-definitions/include/firewall/action-and-notrack.xml.i
@@ -0,0 +1,41 @@
+<!-- include start from firewall/action-and-notrack.xml.i -->
+<leafNode name="action">
+ <properties>
+ <help>Rule action</help>
+ <completionHelp>
+ <list>accept jump notrack reject return drop queue</list>
+ </completionHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>jump</format>
+ <description>Jump to another chain</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reject</format>
+ <description>Reject matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>return</format>
+ <description>Return from the current chain and continue at the next rule of the last chain</description>
+ </valueHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>queue</format>
+ <description>Enqueue packet to userspace</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notrack</format>
+ <description>Igone connection tracking</description>
+ </valueHelp>
+ <constraint>
+ <regex>(accept|jump|notrack|reject|return|drop|queue)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i
new file mode 100644
index 000000000..7a2eb86d4
--- /dev/null
+++ b/interface-definitions/include/firewall/common-rule-inet.xml.i
@@ -0,0 +1,374 @@
+<!-- include start from firewall/common-rule-inet.xml.i -->
+#include <include/firewall/action.xml.i>
+#include <include/generic-description.xml.i>
+#include <include/firewall/dscp.xml.i>
+#include <include/firewall/packet-options.xml.i>
+#include <include/firewall/connection-mark.xml.i>
+#include <include/firewall/nft-queue.xml.i>
+<leafNode name="disable">
+ <properties>
+ <help>Option to disable firewall rule</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<node name="fragment">
+ <properties>
+ <help>IP fragment match</help>
+ </properties>
+ <children>
+ <leafNode name="match-frag">
+ <properties>
+ <help>Second and further fragments of fragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-non-frag">
+ <properties>
+ <help>Head fragments or unfragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ </properties>
+ <children>
+ <leafNode name="match-ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-none">
+ <properties>
+ <help>Inbound non-IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="limit">
+ <properties>
+ <help>Rate limit using a token bucket filter</help>
+ </properties>
+ <children>
+ <leafNode name="burst">
+ <properties>
+ <help>Maximum number of packets to allow in excess of rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum number of packets to allow in excess of rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rate">
+ <properties>
+ <help>Maximum average matching rate</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>integer/unit (Example: 5/minute)</description>
+ </valueHelp>
+ <constraint>
+ <regex>\d+/(second|minute|hour|day)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="log">
+ <properties>
+ <help>Option to log packets matching rule</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable log</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable log</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="log">
+ <properties>
+ <help>Option to log packets matching rule</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable log</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable log</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+#include <include/firewall/rule-log-options.xml.i>
+<node name="connection-status">
+ <properties>
+ <help>Connection status</help>
+ </properties>
+ <children>
+ <leafNode name="nat">
+ <properties>
+ <help>NAT connection status</help>
+ <completionHelp>
+ <list>destination source</list>
+ </completionHelp>
+ <valueHelp>
+ <format>destination</format>
+ <description>Match connections that are subject to destination NAT</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source</format>
+ <description>Match connections that are subject to source NAT</description>
+ </valueHelp>
+ <constraint>
+ <regex>(destination|source)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+</leafNode>
+<node name="recent">
+ <properties>
+ <help>Parameters for matching recently seen sources</help>
+ </properties>
+ <children>
+ <leafNode name="count">
+ <properties>
+ <help>Source addresses seen more than N times</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Source addresses seen more than N times</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="time">
+ <properties>
+ <help>Source addresses seen in the last second/minute/hour</help>
+ <completionHelp>
+ <list>second minute hour</list>
+ </completionHelp>
+ <valueHelp>
+ <format>second</format>
+ <description>Source addresses seen COUNT times in the last second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>minute</format>
+ <description>Source addresses seen COUNT times in the last minute</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hour</format>
+ <description>Source addresses seen COUNT times in the last hour</description>
+ </valueHelp>
+ <constraint>
+ <regex>(second|minute|hour)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="state">
+ <properties>
+ <help>Session state</help>
+ </properties>
+ <children>
+ <leafNode name="established">
+ <properties>
+ <help>Established state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="invalid">
+ <properties>
+ <help>Invalid state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="new">
+ <properties>
+ <help>New state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="related">
+ <properties>
+ <help>Related state</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+#include <include/firewall/tcp-flags.xml.i>
+<node name="time">
+ <properties>
+ <help>Time to match rule</help>
+ </properties>
+ <children>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter date using following notation - YYYY-MM-DD</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\d{4}\-\d{2}\-\d{2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter time using using 24 hour notation - hh:mm:ss</description>
+ </valueHelp>
+ <constraint>
+ <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter date using following notation - YYYY-MM-DD</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\d{4}\-\d{2}\-\d{2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter time using using 24 hour notation - hh:mm:ss</description>
+ </valueHelp>
+ <constraint>
+ <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Comma separated weekdays to match rule on</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-6</format>
+ <description>Day number (0 = Sunday ... 6 = Saturday)</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
new file mode 100644
index 000000000..a1071a09a
--- /dev/null
+++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
@@ -0,0 +1,331 @@
+<!-- include start from firewall/common-rule-ipv4-raw.xml.i -->
+#include <include/firewall/action-and-notrack.xml.i>
+#include <include/generic-description.xml.i>
+#include <include/firewall/dscp.xml.i>
+#include <include/firewall/ttl.xml.i>
+#include <include/firewall/nft-queue.xml.i>
+<node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/address-mask.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ </children>
+</node>
+<leafNode name="disable">
+ <properties>
+ <help>Option to disable firewall rule</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<node name="fragment">
+ <properties>
+ <help>IP fragment match</help>
+ </properties>
+ <children>
+ <leafNode name="match-frag">
+ <properties>
+ <help>Second and further fragments of fragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-non-frag">
+ <properties>
+ <help>Head fragments or unfragmented packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="icmp">
+ <properties>
+ <help>ICMP type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="code">
+ <properties>
+ <help>ICMP code</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP code (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>ICMP type</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP type (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/icmp-type-name.xml.i>
+ </children>
+</node>
+<node name="ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ </properties>
+ <children>
+ <leafNode name="match-ipsec">
+ <properties>
+ <help>Inbound IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="match-none">
+ <properties>
+ <help>Inbound non-IPsec packets</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="limit">
+ <properties>
+ <help>Rate limit using a token bucket filter</help>
+ </properties>
+ <children>
+ <leafNode name="burst">
+ <properties>
+ <help>Maximum number of packets to allow in excess of rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Maximum number of packets to allow in excess of rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rate">
+ <properties>
+ <help>Maximum average matching rate</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>integer/unit (Example: 5/minute)</description>
+ </valueHelp>
+ <constraint>
+ <regex>\d+/(second|minute|hour|day)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="log">
+ <properties>
+ <help>Option to log packets matching rule</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable log</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable log</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+#include <include/firewall/rule-log-options.xml.i>
+<node name="connection-status">
+ <properties>
+ <help>Connection status</help>
+ </properties>
+ <children>
+ <leafNode name="nat">
+ <properties>
+ <help>NAT connection status</help>
+ <completionHelp>
+ <list>destination source</list>
+ </completionHelp>
+ <valueHelp>
+ <format>destination</format>
+ <description>Match connections that are subject to destination NAT</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source</format>
+ <description>Match connections that are subject to source NAT</description>
+ </valueHelp>
+ <constraint>
+ <regex>(destination|source)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+</leafNode>
+<node name="recent">
+ <properties>
+ <help>Parameters for matching recently seen sources</help>
+ </properties>
+ <children>
+ <leafNode name="count">
+ <properties>
+ <help>Source addresses seen more than N times</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Source addresses seen more than N times</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="time">
+ <properties>
+ <help>Source addresses seen in the last second/minute/hour</help>
+ <completionHelp>
+ <list>second minute hour</list>
+ </completionHelp>
+ <valueHelp>
+ <format>second</format>
+ <description>Source addresses seen COUNT times in the last second</description>
+ </valueHelp>
+ <valueHelp>
+ <format>minute</format>
+ <description>Source addresses seen COUNT times in the last minute</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hour</format>
+ <description>Source addresses seen COUNT times in the last hour</description>
+ </valueHelp>
+ <constraint>
+ <regex>(second|minute|hour)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/address-mask.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ </children>
+</node>
+#include <include/firewall/tcp-flags.xml.i>
+<node name="time">
+ <properties>
+ <help>Time to match rule</help>
+ </properties>
+ <children>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter date using following notation - YYYY-MM-DD</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\d{4}\-\d{2}\-\d{2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter time using using 24 hour notation - hh:mm:ss</description>
+ </valueHelp>
+ <constraint>
+ <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter date using following notation - YYYY-MM-DD</description>
+ </valueHelp>
+ <constraint>
+ <regex>(\d{4}\-\d{2}\-\d{2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Enter time using using 24 hour notation - hh:mm:ss</description>
+ </valueHelp>
+ <constraint>
+ <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Comma separated weekdays to match rule on</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-6</format>
+ <description>Day number (0 = Sunday ... 6 = Saturday)</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule-ipv4.xml.i b/interface-definitions/include/firewall/common-rule-ipv4.xml.i
new file mode 100644
index 000000000..4ed179ae7
--- /dev/null
+++ b/interface-definitions/include/firewall/common-rule-ipv4.xml.i
@@ -0,0 +1,72 @@
+<!-- include start from firewall/common-rule-ipv4.xml.i -->
+#include <include/firewall/common-rule-inet.xml.i>
+#include <include/firewall/ttl.xml.i>
+<node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/address-mask.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ </children>
+</node>
+<node name="icmp">
+ <properties>
+ <help>ICMP type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="code">
+ <properties>
+ <help>ICMP code</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP code (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>ICMP type</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMP type (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/icmp-type-name.xml.i>
+ </children>
+</node>
+<leafNode name="jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/address-mask.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/common-rule-ipv6.xml.i b/interface-definitions/include/firewall/common-rule-ipv6.xml.i
new file mode 100644
index 000000000..6219557db
--- /dev/null
+++ b/interface-definitions/include/firewall/common-rule-ipv6.xml.i
@@ -0,0 +1,72 @@
+<!-- include start from firewall/common-rule-ipv6.xml.i -->
+#include <include/firewall/common-rule-inet.xml.i>
+#include <include/firewall/hop-limit.xml.i>
+<node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/address-mask-ipv6.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ </children>
+</node>
+<node name="icmpv6">
+ <properties>
+ <help>ICMPv6 type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="code">
+ <properties>
+ <help>ICMPv6 code</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMPv6 code (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>ICMPv6 type</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>ICMPv6 type (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/icmpv6-type-name.xml.i>
+ </children>
+</node>
+<leafNode name="jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv6 name</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/address-mask-ipv6.xml.i>
+ #include <include/firewall/fqdn.xml.i>
+ #include <include/firewall/geoip.xml.i>
+ #include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/default-action-base-chains.xml.i b/interface-definitions/include/firewall/default-action-base-chains.xml.i
new file mode 100644
index 000000000..aa62abf3d
--- /dev/null
+++ b/interface-definitions/include/firewall/default-action-base-chains.xml.i
@@ -0,0 +1,22 @@
+<!-- include start from firewall/default-action-base-chains.xml.i -->
+<leafNode name="default-action">
+ <properties>
+ <help>Default-action for rule-set</help>
+ <completionHelp>
+ <list>drop accept</list>
+ </completionHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop if no prior rules are hit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept if no prior rules are hit</description>
+ </valueHelp>
+ <constraint>
+ <regex>(drop|accept)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>accept</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
new file mode 100644
index 000000000..a63874cb0
--- /dev/null
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -0,0 +1,252 @@
+<!-- include start from firewall/global-options.xml.i -->
+<node name="global-options">
+ <properties>
+ <help>Global Options</help>
+ </properties>
+ <children>
+ <leafNode name="all-ping">
+ <properties>
+ <help>Policy for handling of all IPv4 ICMP echo requests</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of all IPv4 ICMP echo requests</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of all IPv4 ICMP echo requests</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>enable</defaultValue>
+ </leafNode>
+ <leafNode name="broadcast-ping">
+ <properties>
+ <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="ip-src-route">
+ <properties>
+ <help>Policy for handling IPv4 packets with source route option</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of IPv4 packets with source route option</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of IPv4 packets with source route option</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="log-martians">
+ <properties>
+ <help>Policy for logging IPv4 packets with invalid addresses</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable logging of IPv4 packets with invalid addresses</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable logging of Ipv4 packets with invalid addresses</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>enable</defaultValue>
+ </leafNode>
+ <leafNode name="receive-redirects">
+ <properties>
+ <help>Policy for handling received IPv4 ICMP redirect messages</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of received IPv4 ICMP redirect messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of received IPv4 ICMP redirect messages</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="resolver-cache">
+ <properties>
+ <help>Retains last successful value if domain resolution fails</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="resolver-interval">
+ <properties>
+ <help>Domain resolver update interval</help>
+ <valueHelp>
+ <format>u32:10-3600</format>
+ <description>Interval (seconds)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 10-3600"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+ </leafNode>
+ <leafNode name="send-redirects">
+ <properties>
+ <help>Policy for sending IPv4 ICMP redirect messages</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable sending IPv4 ICMP redirect messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable sending IPv4 ICMP redirect messages</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>enable</defaultValue>
+ </leafNode>
+ <leafNode name="source-validation">
+ <properties>
+ <help>Policy for source validation by reversed path, as specified in RFC3704</help>
+ <completionHelp>
+ <list>strict loose disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>strict</format>
+ <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>loose</format>
+ <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>No source validation</description>
+ </valueHelp>
+ <constraint>
+ <regex>(strict|loose|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="syn-cookies">
+ <properties>
+ <help>Policy for using TCP SYN cookies with IPv4</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable use of TCP SYN cookies with IPv4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable use of TCP SYN cookies with IPv4</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>enable</defaultValue>
+ </leafNode>
+ <leafNode name="twa-hazards-protection">
+ <properties>
+ <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable RFC1337 TIME-WAIT hazards protection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable RFC1337 TIME-WAIT hazards protection</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="ipv6-receive-redirects">
+ <properties>
+ <help>Policy for handling received ICMPv6 redirect messages</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of received ICMPv6 redirect messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of received ICMPv6 redirect messages</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ <leafNode name="ipv6-src-route">
+ <properties>
+ <help>Policy for handling IPv6 packets with routing extension header</help>
+ <completionHelp>
+ <list>enable disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>enable</format>
+ <description>Enable processing of IPv6 packets with routing header type 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>Disable processing of IPv6 packets with routing header</description>
+ </valueHelp>
+ <constraint>
+ <regex>(enable|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/inbound-interface.xml.i b/interface-definitions/include/firewall/inbound-interface.xml.i
new file mode 100644
index 000000000..13df71de3
--- /dev/null
+++ b/interface-definitions/include/firewall/inbound-interface.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from firewall/inbound-interface.xml.i -->
+<node name="inbound-interface">
+ <properties>
+ <help>Match inbound-interface</help>
+ </properties>
+ <children>
+ #include <include/firewall/match-interface.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-custom-name.xml.i b/interface-definitions/include/firewall/ipv4-custom-name.xml.i
new file mode 100644
index 000000000..9d6ecfaf2
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv4-custom-name.xml.i
@@ -0,0 +1,41 @@
+<!-- include start from firewall/ipv4-custom-name.xml.i -->
+<tagNode name="name">
+ <properties>
+ <help>IPv4 custom firewall</help>
+ <constraint>
+ <regex>[a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/firewall/default-action.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
+ #include <include/generic-description.xml.i>
+ <leafNode name="default-jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall custom rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+</tagNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
new file mode 100644
index 000000000..08ee96419
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i
@@ -0,0 +1,36 @@
+<!-- include start from firewall/ipv4-hook-forward.xml.i -->
+<node name="forward">
+ <properties>
+ <help>IPv4 forward firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv4 firewall forward filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall forward filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-hook-input.xml.i b/interface-definitions/include/firewall/ipv4-hook-input.xml.i
new file mode 100644
index 000000000..32b0ec94f
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv4-hook-input.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from firewall/ipv4-hook-input.xml.i -->
+<node name="input">
+ <properties>
+ <help>IPv4 input firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv4 firewall input filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall input filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-hook-output.xml.i b/interface-definitions/include/firewall/ipv4-hook-output.xml.i
new file mode 100644
index 000000000..d50d1e93b
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv4-hook-output.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from firewall/ipv4-hook-output.xml.i -->
+<node name="output">
+ <properties>
+ <help>IPv4 output firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv4 firewall output filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall output filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i b/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i
new file mode 100644
index 000000000..c38918375
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv4-hook-prerouting.xml.i
@@ -0,0 +1,85 @@
+<!-- include start from firewall/ipv4-hook-prerouting.xml.i -->
+<node name="prerouting">
+ <properties>
+ <help>IPv4 prerouting firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv4 firewall prerouting filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall prerouting filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ <leafNode name="jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="raw">
+ <properties>
+ <help>IPv4 firewall prerouting raw</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <leafNode name="default-jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv4 Firewall prerouting raw rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv4-raw.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ <leafNode name="jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv4 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv6-custom-name.xml.i b/interface-definitions/include/firewall/ipv6-custom-name.xml.i
new file mode 100644
index 000000000..81610babf
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv6-custom-name.xml.i
@@ -0,0 +1,41 @@
+<!-- include start from firewall/ipv6-custom-name.xml.i -->
+<tagNode name="name">
+ <properties>
+ <help>IPv6 custom firewall</help>
+ <constraint>
+ <regex>[a-zA-Z0-9][\w\-\.]*</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/firewall/default-action.xml.i>
+ #include <include/firewall/enable-default-log.xml.i>
+ #include <include/generic-description.xml.i>
+ <leafNode name="default-jump-target">
+ <properties>
+ <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
+ <completionHelp>
+ <path>firewall ipv6 name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 Firewall custom rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv6.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+</tagNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
new file mode 100644
index 000000000..20ab8dbe8
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i
@@ -0,0 +1,36 @@
+<!-- include start from firewall/ipv6-hook-forward.xml.i -->
+<node name="forward">
+ <properties>
+ <help>IPv6 forward firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv6 firewall forward filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 Firewall forward filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv6.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv6-hook-input.xml.i b/interface-definitions/include/firewall/ipv6-hook-input.xml.i
new file mode 100644
index 000000000..e34958f28
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv6-hook-input.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from firewall/ipv6-hook-input.xml.i -->
+<node name="input">
+ <properties>
+ <help>IPv6 input firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv6 firewall input filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 Firewall input filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv6.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/ipv6-hook-output.xml.i b/interface-definitions/include/firewall/ipv6-hook-output.xml.i
new file mode 100644
index 000000000..eb4ea7ac3
--- /dev/null
+++ b/interface-definitions/include/firewall/ipv6-hook-output.xml.i
@@ -0,0 +1,35 @@
+<!-- include start from firewall/ipv6-hook-output.xml.i -->
+<node name="output">
+ <properties>
+ <help>IPv6 output firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>IPv6 firewall output filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 Firewall output filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-ipv6.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i
index 3e52422cf..7810f88ab 100644
--- a/interface-definitions/include/firewall/match-interface.xml.i
+++ b/interface-definitions/include/firewall/match-interface.xml.i
@@ -5,6 +5,21 @@
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt*</format>
+ <description>Interface name with wildcard</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!txt</format>
+ <description>Inverted interface name to match</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name-with-wildcard-and-inverted.xml.i>
+ </constraint>
</properties>
</leafNode>
<leafNode name="interface-group">
@@ -13,6 +28,14 @@
<completionHelp>
<path>firewall group interface-group</path>
</completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface-group name to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!txt</format>
+ <description>Inverted interface-group name to match</description>
+ </valueHelp>
</properties>
</leafNode>
<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/outbound-interface.xml.i b/interface-definitions/include/firewall/outbound-interface.xml.i
new file mode 100644
index 000000000..8654dfd80
--- /dev/null
+++ b/interface-definitions/include/firewall/outbound-interface.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from firewall/outbound-interface.xml.i -->
+<node name="outbound-interface">
+ <properties>
+ <help>Match outbound-interface</help>
+ </properties>
+ <children>
+ #include <include/firewall/match-interface.xml.i>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/interface/ipv6-accept-dad.xml.i b/interface-definitions/include/interface/ipv6-accept-dad.xml.i
new file mode 100644
index 000000000..7554b270a
--- /dev/null
+++ b/interface-definitions/include/interface/ipv6-accept-dad.xml.i
@@ -0,0 +1,20 @@
+<!-- include start from interface/ipv6-accept-dad.xml.i -->
+<leafNode name="accept-dad">
+ <properties>
+ <help>Accept Duplicate Address Detection</help>
+ <valueHelp>
+ <format>0</format>
+ <description>Disable DAD</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1</format>
+ <description>Enable DAD</description>
+ </valueHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>Enable DAD - disable IPv6 if MAC-based duplicate link-local address found</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>1</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i
index babe6d20f..3b9294dd0 100644
--- a/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i
+++ b/interface-definitions/include/interface/ipv6-dup-addr-detect-transmits.xml.i
@@ -1,7 +1,7 @@
<!-- include start from interface/ipv6-dup-addr-detect-transmits.xml.i -->
<leafNode name="dup-addr-detect-transmits">
<properties>
- <help>Number of NS messages to send while performing DAD (default: 1)</help>
+ <help>Number of NS messages to send while performing DAD</help>
<valueHelp>
<format>u32:0</format>
<description>Disable Duplicate Address Dectection (DAD)</description>
@@ -14,5 +14,6 @@
<validator name="numeric" argument="--non-negative"/>
</constraint>
</properties>
+ <defaultValue>1</defaultValue>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i
index f740ce0c2..d2e47de91 100644
--- a/interface-definitions/include/interface/ipv6-options.xml.i
+++ b/interface-definitions/include/interface/ipv6-options.xml.i
@@ -6,6 +6,7 @@
<children>
#include <include/interface/adjust-mss.xml.i>
#include <include/interface/disable-forwarding.xml.i>
+ #include <include/interface/ipv6-accept-dad.xml.i>
#include <include/interface/ipv6-address.xml.i>
#include <include/interface/ipv6-dup-addr-detect-transmits.xml.i>
</children>
diff --git a/interface-definitions/include/interface/mac-multi.xml.i b/interface-definitions/include/interface/mac-multi.xml.i
new file mode 100644
index 000000000..458372e67
--- /dev/null
+++ b/interface-definitions/include/interface/mac-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/mac-multi.xml.i -->
+<leafNode name="mac">
+ <properties>
+ <help>Media Access Control (MAC) address</help>
+ <valueHelp>
+ <format>macaddr</format>
+ <description>Hardware (MAC) address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/macsec-key.xml.i b/interface-definitions/include/interface/macsec-key.xml.i
new file mode 100644
index 000000000..5a857a612
--- /dev/null
+++ b/interface-definitions/include/interface/macsec-key.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/macsec-key.xml.i -->
+<leafNode name="key">
+ <properties>
+ <help>MACsec static key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description>
+ </valueHelp>
+ <constraint>
+ <regex>[A-Fa-f0-9]{32}</regex>
+ <regex>[A-Fa-f0-9]{64}</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/per-client-thread.xml.i b/interface-definitions/include/interface/per-client-thread.xml.i
new file mode 100644
index 000000000..2fd19b5ce
--- /dev/null
+++ b/interface-definitions/include/interface/per-client-thread.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/per-client-thread.xml.i -->
+<leafNode name="per-client-thread">
+ <properties>
+ <help>Process traffic from each client in a dedicated thread</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/radius-server-key.xml.i b/interface-definitions/include/radius-server-key.xml.i
index c6301646b..dd5cdb0c6 100644
--- a/interface-definitions/include/radius-server-key.xml.i
+++ b/interface-definitions/include/radius-server-key.xml.i
@@ -2,6 +2,14 @@
<leafNode name="key">
<properties>
<help>Shared secret key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Password string (key)</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,128}</regex>
+ </constraint>
+ <constraintErrorMessage>Password must be less then 128 characters</constraintErrorMessage>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i
index c32484542..dd21bfaca 100644
--- a/interface-definitions/include/version/firewall-version.xml.i
+++ b/interface-definitions/include/version/firewall-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
-<syntaxVersion component='firewall' version='10'></syntaxVersion>
+<syntaxVersion component='firewall' version='11'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index 6bc28e44b..766b0bede 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -52,6 +52,28 @@
<valueless/>
</properties>
</leafNode>
+ <node name="static">
+ <properties>
+ <help>Use static keys for MACsec [static Secure Authentication Key (SAK) mode]</help>
+ </properties>
+ <children>
+ #include <include/interface/macsec-key.xml.i>
+ <tagNode name="peer">
+ <properties>
+ <help>MACsec peer name</help>
+ <constraint>
+ <regex>[^ ]{1,100}</regex>
+ </constraint>
+ <constraintErrorMessage>MACsec peer name exceeds limit of 100 characters</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/interface/mac.xml.i>
+ #include <include/interface/macsec-key.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="mka">
<properties>
<help>MACsec Key Agreement protocol (MKA)</help>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 127a8179b..831659250 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -752,6 +752,16 @@
</completionHelp>
</properties>
</leafNode>
+ <leafNode name="peer-fingerprint">
+ <properties>
+ <multi/>
+ <help>Peer certificate SHA256 fingerprint</help>
+ <constraint>
+ <regex>[0-9a-fA-F]{2}:([0-9a-fA-F]{2}:){30}[0-9a-fA-F]{2}</regex>
+ </constraint>
+ <constraintErrorMessage>Peer certificate fingerprint must be a colon-separated SHA256 hex digest</constraintErrorMessage>
+ </properties>
+ </leafNode>
<leafNode name="tls-version-min">
<properties>
<help>Specify the minimum required TLS version</help>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index dd1e8e511..3c79cef28 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -59,6 +59,7 @@
</properties>
<children>
#include <include/generic-disable-node.xml.i>
+ #include <include/generic-description.xml.i>
<leafNode name="public-key">
<properties>
<help>base64 encoded public key</help>
@@ -119,6 +120,7 @@
</children>
</tagNode>
#include <include/interface/redirect.xml.i>
+ #include <include/interface/per-client-thread.xml.i>
#include <include/interface/vrf.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index a9538d577..88b858c07 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -595,6 +595,49 @@
<help>Wireless security settings</help>
</properties>
<children>
+ <node name="station-address">
+ <properties>
+ <help>Station MAC address based authentication</help>
+ </properties>
+ <children>
+ <leafNode name="mode">
+ <properties>
+ <help>Select security operation mode</help>
+ <completionHelp>
+ <list>accept deny</list>
+ </completionHelp>
+ <valueHelp>
+ <format>accept</format>
+ <description>Accept all clients unless found in deny list</description>
+ </valueHelp>
+ <valueHelp>
+ <format>deny</format>
+ <description>Deny all clients unless found in accept list</description>
+ </valueHelp>
+ <constraint>
+ <regex>(accept|deny)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>accept</defaultValue>
+ </leafNode>
+ <node name="accept">
+ <properties>
+ <help>Accept station MAC address</help>
+ </properties>
+ <children>
+ #include <include/interface/mac-multi.xml.i>
+ </children>
+ </node>
+ <node name="deny">
+ <properties>
+ <help>Deny station MAC address</help>
+ </properties>
+ <children>
+ #include <include/interface/mac-multi.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="wep">
<properties>
<help>Wired Equivalent Privacy (WEP) parameters</help>
@@ -778,6 +821,7 @@
</properties>
<defaultValue>monitor</defaultValue>
</leafNode>
+ #include <include/interface/per-client-thread.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vif.xml.i>
#include <include/interface/vif-s.xml.i>
diff --git a/interface-definitions/service-monitoring-zabbix-agent.xml.in b/interface-definitions/service-monitoring-zabbix-agent.xml.in
new file mode 100644
index 000000000..40f2df642
--- /dev/null
+++ b/interface-definitions/service-monitoring-zabbix-agent.xml.in
@@ -0,0 +1,193 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="monitoring">
+ <children>
+ <node name="zabbix-agent" owner="${vyos_conf_scripts_dir}/service_monitoring_zabbix-agent.py">
+ <properties>
+ <help>Zabbix-agent settings</help>
+ </properties>
+ <children>
+ <leafNode name="directory">
+ <properties>
+ <help>Folder containing individual Zabbix-agent configuration files</help>
+ <constraint>
+ <validator name="file-path" argument="--directory"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="host-name">
+ <properties>
+ <help>Zabbix agent hostname</help>
+ <constraint>
+ #include <include/constraint/host-name.xml.i>
+ </constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="limits">
+ <properties>
+ <help>Limit settings</help>
+ </properties>
+ <children>
+ <leafNode name="buffer-flush-interval">
+ <properties>
+ <help>Do not keep data longer than N seconds in buffer</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ <constraintErrorMessage>buffer-flush-interval must be between 1 and 3600 seconds</constraintErrorMessage>
+ </properties>
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ <leafNode name="buffer-size">
+ <properties>
+ <help>Maximum number of values in a memory buffer</help>
+ <valueHelp>
+ <format>u32:2-65535</format>
+ <description>Maximum number of values in a memory buffer</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 2-65535"/>
+ </constraint>
+ <constraintErrorMessage>Buffer-size must be between 2 and 65535</constraintErrorMessage>
+ </properties>
+ <defaultValue>100</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <node name="log">
+ <properties>
+ <help>Log settings</help>
+ </properties>
+ <children>
+ <leafNode name="debug-level">
+ <properties>
+ <help>Debug level</help>
+ <completionHelp>
+ <list>basic critical error warning debug extended-debug</list>
+ </completionHelp>
+ <valueHelp>
+ <format>basic</format>
+ <description>Basic information</description>
+ </valueHelp>
+ <valueHelp>
+ <format>critical</format>
+ <description>Critical information</description>
+ </valueHelp>
+ <valueHelp>
+ <format>error</format>
+ <description>Error information</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warnings</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug information</description>
+ </valueHelp>
+ <valueHelp>
+ <format>extended-debug</format>
+ <description>Extended debug information</description>
+ </valueHelp>
+ <constraint>
+ <regex>(basic|critical|error|warning|debug|extended-debug)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>warning</defaultValue>
+ </leafNode>
+ <leafNode name="remote-commands">
+ <properties>
+ <help>Enable logging of executed shell commands as warnings</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="size">
+ <properties>
+ <help>Log file size in megabytes</help>
+ <valueHelp>
+ <format>u32:0-1024</format>
+ <description>Megabytes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-1024"/>
+ </constraint>
+ <constraintErrorMessage>Size must be between 0 and 1024 Megabytes</constraintErrorMessage>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/listen-address.xml.i>
+ <leafNode name="listen-address">
+ <defaultValue>0.0.0.0</defaultValue>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>10050</defaultValue>
+ </leafNode>
+ <leafNode name="server">
+ <properties>
+ <help>Remote server to connect to</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Server IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Server hostname/FQDN</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <tagNode name="server-active">
+ <properties>
+ <help>Remote server address to get active checks from</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Server IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Server hostname/FQDN</description>
+ </valueHelp>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ </children>
+ </tagNode>
+ <leafNode name="timeout">
+ <properties>
+ <help>Item processing timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-30</format>
+ <description>Item processing timeout</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-30"/>
+ </constraint>
+ <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage>
+ </properties>
+ <defaultValue>3</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index d772c7821..71db8b1d6 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -184,6 +184,13 @@
<leafNode name="home-directory">
<properties>
<help>Home directory</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Path to home directory</description>
+ </valueHelp>
+ <constraint>
+ <regex>\/$|(\/[a-zA-Z_0-9-.]+)+</regex>
+ </constraint>
</properties>
</leafNode>
</children>
diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in
index b5dee7c9e..164ce6b60 100644
--- a/op-mode-definitions/firewall.xml.in
+++ b/op-mode-definitions/firewall.xml.in
@@ -131,46 +131,206 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group</command>
</leafNode>
- <tagNode name="ipv6-name">
+ <node name="ipv6">
<properties>
- <help>Show IPv6 firewall chains</help>
- <completionHelp>
- <path>firewall ipv6-name</path>
- </completionHelp>
+ <help>Show IPv6 firewall</help>
</properties>
<children>
- <tagNode name="rule">
+ <node name="forward">
+ <properties>
+ <help>Show IPv6 forward firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv6 forward filter firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 forward filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6 forward filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command>
+ </node>
+ </children>
+ </node>
+ <node name="input">
+ <properties>
+ <help>Show IPv6 input firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv6 forward input firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 input filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6 input filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command>
+ </node>
+ </children>
+ </node>
+ <node name="output">
+ <properties>
+ <help>Show IPv6 output firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv6 output filter firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 output filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6 output filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="ipv6-name">
<properties>
- <help>Show summary of IPv6 firewall rules</help>
+ <help>Show IPv6 custom firewall chains</help>
<completionHelp>
- <path>firewall ipv6-name ${COMP_WORDS[6]} rule</path>
+ <path>firewall ipv6 ipv6-name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --rule $6 --ipv6</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 custom firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv6 ipv6-name ${COMP_WORDS[6]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --ipv6</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --ipv6</command>
- </tagNode>
- <tagNode name="name">
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command>
+ </node>
+ <node name="ipv4">
<properties>
- <help>Show IPv4 firewall chains</help>
- <completionHelp>
- <path>firewall name</path>
- </completionHelp>
+ <help>Show IPv4 firewall</help>
</properties>
<children>
- <tagNode name="rule">
+ <node name="forward">
+ <properties>
+ <help>Show IPv4 forward firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv4 forward filter firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 forward filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv4 forward filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
+ <node name="input">
+ <properties>
+ <help>Show IPv4 input firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv4 forward input firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 input filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv4 input filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
+ <node name="output">
+ <properties>
+ <help>Show IPv4 output firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show IPv4 output filter firewall ruleset</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 output filter firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv4 output filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
+ <tagNode name="name">
<properties>
- <help>Show summary of IPv4 firewall rules</help>
+ <help>Show IPv4 custom firewall chains</help>
<completionHelp>
- <path>firewall name ${COMP_WORDS[6]} rule</path>
+ <path>firewall ipv4 name</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --rule $6</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 custom firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv4 name ${COMP_WORDS[6]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --hook $4 --priority $5</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4</command>
- </tagNode>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_family --family $3</command>
+ </node>
<leafNode name="statistics">
<properties>
<help>Show statistics of firewall application</help>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index ee52a7eb8..52b5b85d4 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -36,6 +36,12 @@
</properties>
<command>journalctl --no-hostname --follow --boot --unit conntrackd.service</command>
</leafNode>
+ <leafNode name="console-server">
+ <properties>
+ <help>Monitor last lines of console server log</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit conserver-server.service</command>
+ </leafNode>
<node name="dhcp">
<properties>
<help>Monitor last lines of Dynamic Host Control Protocol log</help>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index c5abf86cd..ca0eb3687 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -535,6 +535,15 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --pem</command>
</leafNode>
+ <tagNode name="fingerprint">
+ <properties>
+ <help>Show x509 certificate fingerprint</help>
+ <completionHelp>
+ <list>sha256 sha384 sha512</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --fingerprint "$6"</command>
+ </tagNode>
</children>
</tagNode>
<leafNode name="crl">
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 925a780ac..747622db6 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -50,6 +50,12 @@
</properties>
<command>journalctl --no-hostname --boot --unit conntrackd.service</command>
</leafNode>
+ <leafNode name="console-server">
+ <properties>
+ <help>Show log for console server</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit conserver-server.service</command>
+ </leafNode>
<node name="ids">
<properties>
<help>Show log for for Intrusion Detection System</help>
diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py
index a4e318d08..84e0ae51a 100644
--- a/python/vyos/component_version.py
+++ b/python/vyos/component_version.py
@@ -37,7 +37,7 @@ import re
import sys
import fileinput
-from vyos.xml import component_version
+from vyos.xml_ref import component_version
from vyos.version import get_version
from vyos.defaults import directories
diff --git a/python/vyos/config.py b/python/vyos/config.py
index 179f60c43..6fececd76 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -66,17 +66,31 @@ In operational mode, all functions return values from the running config.
import re
import json
from copy import deepcopy
+from typing import Union
import vyos.configtree
-from vyos.xml_ref import multi_to_list, from_source
-from vyos.xml_ref import merge_defaults, relative_defaults
-from vyos.utils.dict import get_sub_dict, mangle_dict_keys
-from vyos.configsource import ConfigSource, ConfigSourceSession
+from vyos.xml_ref import multi_to_list
+from vyos.xml_ref import from_source
+from vyos.xml_ref import ext_dict_merge
+from vyos.xml_ref import relative_defaults
+from vyos.utils.dict import get_sub_dict
+from vyos.utils.dict import mangle_dict_keys
+from vyos.configsource import ConfigSource
+from vyos.configsource import ConfigSourceSession
class ConfigDict(dict):
_from_defaults = {}
- def from_defaults(self, path: list[str]):
+ _dict_kwargs = {}
+ def from_defaults(self, path: list[str]) -> bool:
return from_source(self._from_defaults, path)
+ @property
+ def kwargs(self) -> dict:
+ return self._dict_kwargs
+
+def config_dict_merge(src: dict, dest: Union[dict, ConfigDict]) -> ConfigDict:
+ if not isinstance(dest, ConfigDict):
+ dest = ConfigDict(dest)
+ return ext_dict_merge(src, dest)
class Config(object):
"""
@@ -229,6 +243,13 @@ class Config(object):
return config_dict
+ def verify_mangling(self, key_mangling):
+ if not (isinstance(key_mangling, tuple) and \
+ (len(key_mangling) == 2) and \
+ isinstance(key_mangling[0], str) and \
+ isinstance(key_mangling[1], str)):
+ raise ValueError("key_mangling must be a tuple of two strings")
+
def get_config_dict(self, path=[], effective=False, key_mangling=None,
get_first_key=False, no_multi_convert=False,
no_tag_node_value_mangle=False,
@@ -243,44 +264,37 @@ class Config(object):
Returns: a dict representation of the config under path
"""
+ kwargs = locals().copy()
+ del kwargs['self']
+ del kwargs['no_multi_convert']
+ del kwargs['with_defaults']
+ del kwargs['with_recursive_defaults']
+
lpath = self._make_path(path)
root_dict = self.get_cached_root_dict(effective)
conf_dict = get_sub_dict(root_dict, lpath, get_first_key=get_first_key)
- if key_mangling is None and no_multi_convert and not (with_defaults or with_recursive_defaults):
- return deepcopy(conf_dict)
-
rpath = lpath if get_first_key else lpath[:-1]
if not no_multi_convert:
conf_dict = multi_to_list(rpath, conf_dict)
+ if key_mangling is not None:
+ self.verify_mangling(key_mangling)
+ conf_dict = mangle_dict_keys(conf_dict,
+ key_mangling[0], key_mangling[1],
+ abs_path=rpath,
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
+
if with_defaults or with_recursive_defaults:
+ defaults = self.get_config_defaults(**kwargs,
+ recursive=with_recursive_defaults)
+ conf_dict = config_dict_merge(defaults, conf_dict)
+ else:
conf_dict = ConfigDict(conf_dict)
- conf_dict = merge_defaults(lpath, conf_dict,
- get_first_key=get_first_key,
- recursive=with_recursive_defaults)
- if key_mangling is None:
- return conf_dict
-
- if not (isinstance(key_mangling, tuple) and \
- (len(key_mangling) == 2) and \
- isinstance(key_mangling[0], str) and \
- isinstance(key_mangling[1], str)):
- raise ValueError("key_mangling must be a tuple of two strings")
-
- def mangle(obj):
- return mangle_dict_keys(obj, key_mangling[0], key_mangling[1],
- abs_path=rpath,
- no_tag_node_value_mangle=no_tag_node_value_mangle)
-
- if isinstance(conf_dict, ConfigDict):
- from_defaults = mangle(conf_dict._from_defaults)
- conf_dict = mangle(conf_dict)
- conf_dict._from_defaults = from_defaults
- else:
- conf_dict = mangle(conf_dict)
+ # save optional args for a call to get_config_defaults
+ setattr(conf_dict, '_dict_kwargs', kwargs)
return conf_dict
@@ -294,21 +308,29 @@ class Config(object):
defaults = relative_defaults(lpath, conf_dict,
get_first_key=get_first_key,
recursive=recursive)
- if key_mangling is None:
- return defaults
rpath = lpath if get_first_key else lpath[:-1]
- if not (isinstance(key_mangling, tuple) and \
- (len(key_mangling) == 2) and \
- isinstance(key_mangling[0], str) and \
- isinstance(key_mangling[1], str)):
- raise ValueError("key_mangling must be a tuple of two strings")
-
- defaults = mangle_dict_keys(defaults, key_mangling[0], key_mangling[1], abs_path=rpath, no_tag_node_value_mangle=no_tag_node_value_mangle)
+ if key_mangling is not None:
+ self.verify_mangling(key_mangling)
+ defaults = mangle_dict_keys(defaults,
+ key_mangling[0], key_mangling[1],
+ abs_path=rpath,
+ no_tag_node_value_mangle=no_tag_node_value_mangle)
return defaults
+ def merge_defaults(self, config_dict: ConfigDict, recursive=False):
+ if not isinstance(config_dict, ConfigDict):
+ raise TypeError('argument is not of type ConfigDict')
+ if not config_dict.kwargs:
+ raise ValueError('argument missing metadata')
+
+ args = config_dict.kwargs
+ d = self.get_config_defaults(**args, recursive=recursive)
+ config_dict = config_dict_merge(d, config_dict)
+ return config_dict
+
def is_multi(self, path):
"""
Args:
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 2a47e88f9..71a06b625 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -20,7 +20,6 @@ import os
import json
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos.utils.process import cmd
def retrieve_config(path_hash, base_path, config):
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 0caa204c3..1ec2dfafe 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -22,7 +22,7 @@ from vyos.configdict import list_diff
from vyos.utils.dict import get_sub_dict
from vyos.utils.dict import mangle_dict_keys
from vyos.utils.dict import dict_search_args
-from vyos.xml import defaults
+from vyos.xml_ref import get_defaults
class ConfigDiffError(Exception):
"""
@@ -240,7 +240,9 @@ class ConfigDiff(object):
if self._key_mangling:
ret[k] = self._mangle_dict_keys(ret[k])
if k in target_defaults and not no_defaults:
- default_values = defaults(self._make_path(path))
+ default_values = get_defaults(self._make_path(path),
+ get_first_key=True,
+ recursive=True)
ret[k] = dict_merge(default_values, ret[k])
return ret
@@ -264,7 +266,9 @@ class ConfigDiff(object):
ret[k] = self._mangle_dict_keys(ret[k])
if k in target_defaults and not no_defaults:
- default_values = defaults(self._make_path(path))
+ default_values = get_defaults(self._make_path(path),
+ get_first_key=True,
+ recursive=True)
ret[k] = dict_merge(default_values, ret[k])
return ret
@@ -312,7 +316,9 @@ class ConfigDiff(object):
if self._key_mangling:
ret[k] = self._mangle_dict_keys(ret[k])
if k in target_defaults and not no_defaults:
- default_values = defaults(self._make_path(path))
+ default_values = get_defaults(self._make_path(path),
+ get_first_key=True,
+ recursive=True)
ret[k] = dict_merge(default_values, ret[k])
return ret
@@ -335,7 +341,9 @@ class ConfigDiff(object):
ret[k] = self._mangle_dict_keys(ret[k])
if k in target_defaults and not no_defaults:
- default_values = defaults(self._make_path(path))
+ default_values = get_defaults(self._make_path(path),
+ get_first_key=True,
+ recursive=True)
ret[k] = dict_merge(default_values, ret[k])
return ret
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index e18d9817d..09cfd43d3 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -383,14 +383,16 @@ def union(left, right, libpath=LIBPATH):
return tree
def reference_tree_to_json(from_dir, to_file, libpath=LIBPATH):
- __lib = cdll.LoadLibrary(libpath)
- __reference_tree_to_json = __lib.reference_tree_to_json
- __reference_tree_to_json.argtypes = [c_char_p, c_char_p]
- __get_error = __lib.get_error
- __get_error.argtypes = []
- __get_error.restype = c_char_p
-
- res = __reference_tree_to_json(from_dir.encode(), to_file.encode())
+ try:
+ __lib = cdll.LoadLibrary(libpath)
+ __reference_tree_to_json = __lib.reference_tree_to_json
+ __reference_tree_to_json.argtypes = [c_char_p, c_char_p]
+ __get_error = __lib.get_error
+ __get_error.argtypes = []
+ __get_error.restype = c_char_p
+ res = __reference_tree_to_json(from_dir.encode(), to_file.encode())
+ except Exception as e:
+ raise ConfigTreeError(e)
if res == 1:
msg = __get_error().decode()
raise ConfigTreeError(msg)
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 903cc8535..53ff8259e 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -41,14 +41,19 @@ def fqdn_config_parse(firewall):
firewall['ip6_fqdn'] = {}
for domain, path in dict_search_recursive(firewall, 'fqdn'):
- fw_name = path[1] # name/ipv6-name
- rule = path[3] # rule id
- suffix = path[4][0] # source/destination (1 char)
- set_name = f'{fw_name}_{rule}_{suffix}'
-
- if path[0] == 'name':
+ hook_name = path[1]
+ priority = path[2]
+
+ fw_name = path[2]
+ rule = path[4]
+ suffix = path[5][0]
+ set_name = f'{hook_name}_{priority}_{rule}_{suffix}'
+
+ if (path[0] == 'ipv4') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'):
firewall['ip_fqdn'][set_name] = domain
- elif path[0] == 'ipv6_name':
+ elif (path[0] == 'ipv6') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'):
+ if path[1] == 'name':
+ set_name = f'name6_{priority}_{rule}_{suffix}'
firewall['ip6_fqdn'][set_name] = domain
def fqdn_resolve(fqdn, ipv6=False):
@@ -80,7 +85,7 @@ def nft_action(vyos_action):
return 'return'
return vyos_action
-def parse_rule(rule_conf, fw_name, rule_id, ip_name):
+def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output = []
def_suffix = '6' if ip_name == 'ip6' else ''
@@ -129,16 +134,34 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'fqdn' in side_conf:
fqdn = side_conf['fqdn']
+ hook_name = ''
operator = ''
if fqdn[0] == '!':
operator = '!='
- output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{fw_name}_{rule_id}_{prefix}')
+ if hook == 'FWD':
+ hook_name = 'forward'
+ if hook == 'INP':
+ hook_name = 'input'
+ if hook == 'OUT':
+ hook_name = 'output'
+ if hook == 'NAM':
+ hook_name = f'name{def_suffix}'
+ output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{hook_name}_{fw_name}_{rule_id}_{prefix}')
if dict_search_args(side_conf, 'geoip', 'country_code'):
operator = ''
+ hook_name = ''
if dict_search_args(side_conf, 'geoip', 'inverse_match') != None:
operator = '!='
- output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC_{fw_name}_{rule_id}')
+ if hook == 'FWD':
+ hook_name = 'forward'
+ if hook == 'INP':
+ hook_name = 'input'
+ if hook == 'OUT':
+ hook_name = 'output'
+ if hook == 'NAM':
+ hook_name = f'name'
+ output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC{def_suffix}_{hook_name}_{fw_name}_{rule_id}')
if 'mac_address' in side_conf:
suffix = side_conf["mac_address"]
@@ -249,20 +272,34 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
output.append(f'ip6 hoplimit {operator} {value}')
if 'inbound_interface' in rule_conf:
+ operator = ''
if 'interface_name' in rule_conf['inbound_interface']:
iiface = rule_conf['inbound_interface']['interface_name']
- output.append(f'iifname {{{iiface}}}')
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} {{{iiface}}}')
else:
iiface = rule_conf['inbound_interface']['interface_group']
- output.append(f'iifname @I_{iiface}')
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} @I_{iiface}')
if 'outbound_interface' in rule_conf:
+ operator = ''
if 'interface_name' in rule_conf['outbound_interface']:
oiface = rule_conf['outbound_interface']['interface_name']
- output.append(f'oifname {{{oiface}}}')
+ if oiface[0] == '!':
+ operator = '!='
+ oiface = oiface[1:]
+ output.append(f'oifname {operator} {{{oiface}}}')
else:
oiface = rule_conf['outbound_interface']['interface_group']
- output.append(f'oifname @I_{oiface}')
+ if oiface[0] == '!':
+ operator = '!='
+ oiface = oiface[1:]
+ output.append(f'oifname {operator} @I_{oiface}')
if 'ttl' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
@@ -324,7 +361,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'recent' in rule_conf:
count = rule_conf['recent']['count']
time = rule_conf['recent']['time']
- output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
+ output.append(f'add @RECENT{def_suffix}_{hook}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
if 'time' in rule_conf:
output.append(parse_time(rule_conf['time']))
@@ -348,7 +385,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
output.append(parse_policy_set(rule_conf['set'], def_suffix))
if 'action' in rule_conf:
- output.append(nft_action(rule_conf['action']))
+ # Change action=return to action=action
+ # #output.append(nft_action(rule_conf['action']))
+ output.append(f'{rule_conf["action"]}')
if 'jump' in rule_conf['action']:
target = rule_conf['jump_target']
output.append(f'NAME{def_suffix}_{target}')
@@ -365,7 +404,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
else:
output.append('return')
- output.append(f'comment "{fw_name}-{rule_id}"')
+ output.append(f'comment "{hook}-{fw_name}-{rule_id}"')
return " ".join(output)
def parse_tcp_flags(flags):
@@ -493,11 +532,12 @@ def geoip_update(firewall, force=False):
# Map country codes to set names
for codes, path in dict_search_recursive(firewall, 'country_code'):
- set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
- if path[0] == 'name':
+ set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
+ if ( path[0] == 'ipv4'):
for code in codes:
ipv4_codes.setdefault(code, []).append(set_name)
- elif path[0] == 'ipv6_name':
+ elif ( path[0] == 'ipv6' ):
+ set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
for code in codes:
ipv6_codes.setdefault(code, []).append(set_name)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 75c5f27a9..ddac387e7 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2023 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
@@ -191,6 +191,10 @@ class Interface(Control):
'validate': lambda fwd: assert_range(fwd,0,2),
'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding',
},
+ 'ipv6_accept_dad': {
+ 'validate': lambda dad: assert_range(dad,0,3),
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad',
+ },
'ipv6_dad_transmits': {
'validate': assert_positive,
'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits',
@@ -220,6 +224,10 @@ class Interface(Control):
'validate': lambda link: assert_range(link,0,3),
'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
},
+ 'per_client_thread': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/threaded',
+ },
}
_sysfs_get = {
@@ -256,6 +264,9 @@ class Interface(Control):
'ipv6_forwarding': {
'location': '/proc/sys/net/ipv6/conf/{ifname}/forwarding',
},
+ 'ipv6_accept_dad': {
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_dad',
+ },
'ipv6_dad_transmits': {
'location': '/proc/sys/net/ipv6/conf/{ifname}/dad_transmits',
},
@@ -268,6 +279,10 @@ class Interface(Control):
'link_detect': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
},
+ 'per_client_thread': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/threaded',
+ },
}
@classmethod
@@ -846,6 +861,13 @@ class Interface(Control):
return None
return self.set_interface('ipv6_forwarding', forwarding)
+ def set_ipv6_dad_accept(self, dad):
+ """Whether to accept DAD (Duplicate Address Detection)"""
+ tmp = self.get_interface('ipv6_accept_dad')
+ if tmp == dad:
+ return None
+ return self.set_interface('ipv6_accept_dad', dad)
+
def set_ipv6_dad_messages(self, dad):
"""
The amount of Duplicate Address Detection probes to send.
@@ -1372,6 +1394,30 @@ class Interface(Control):
f'egress redirect dev {target_if}')
if err: print('tc filter add for redirect failed')
+ def set_per_client_thread(self, enable):
+ """
+ Per-device control to enable/disable the threaded mode for all the napi
+ instances of the given network device, without the need for a device up/down.
+
+ User sets it to 1 or 0 to enable or disable threaded mode.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('wg1').set_per_client_thread(1)
+ """
+ # In the case of a "virtual" interface like wireguard, the sysfs
+ # node is only created once there is a peer configured. We can now
+ # add a verify() code-path for this or make this dynamic without
+ # nagging the user
+ tmp = self._sysfs_get['per_client_thread']['location']
+ if not os.path.exists(tmp):
+ return None
+
+ tmp = self.get_interface('per_client_thread')
+ if tmp == enable:
+ return None
+ self.set_interface('per_client_thread', enable)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -1551,10 +1597,17 @@ class Interface(Control):
value = '1' if (tmp != None) else '0'
self.set_ipv6_autoconf(value)
- # IPv6 Duplicate Address Detection (DAD) tries
+ # Whether to accept IPv6 DAD (Duplicate Address Detection) packets
+ tmp = dict_search('ipv6.accept_dad', config)
+ # Not all interface types got this CLI option, but if they do, there
+ # is an XML defaultValue available
+ if (tmp != None): self.set_ipv6_dad_accept(tmp)
+
+ # IPv6 DAD tries
tmp = dict_search('ipv6.dup_addr_detect_transmits', config)
- value = tmp if (tmp != None) else '1'
- self.set_ipv6_dad_messages(value)
+ # Not all interface types got this CLI option, but if they do, there
+ # is an XML defaultValue available
+ if (tmp != None): self.set_ipv6_dad_messages(tmp)
# Delete old IPv6 EUI64 addresses before changing MAC
for addr in (dict_search('ipv6.address.eui64_old', config) or []):
@@ -1580,6 +1633,11 @@ class Interface(Control):
# configure interface mirror or redirection target
self.set_mirror_redirect()
+ # enable/disable NAPI threading mode
+ tmp = dict_search('per_client_thread', config)
+ value = '1' if (tmp != None) else '0'
+ self.set_per_client_thread(value)
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py
index 1a78d18d8..9329c5ee7 100644
--- a/python/vyos/ifconfig/macsec.py
+++ b/python/vyos/ifconfig/macsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2023 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
@@ -41,10 +41,30 @@ class MACsecIf(Interface):
Create MACsec interface in OS kernel. Interface is administrative
down by default.
"""
+
# create tunnel interface
cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config)
cmd += f' cipher {self.config["security"]["cipher"]}'
self._cmd(cmd)
+ # Check if using static keys
+ if 'static' in self.config["security"]:
+ # Set static TX key
+ cmd = 'ip macsec add {ifname} tx sa 0 pn 1 on key 00'.format(**self.config)
+ cmd += f' {self.config["security"]["static"]["key"]}'
+ self._cmd(cmd)
+
+ for peer, peer_config in self.config["security"]["static"]["peer"].items():
+ if 'disable' in peer_config:
+ continue
+
+ # Create the address
+ cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config)
+ cmd += f' {peer_config["mac"]}'
+ self._cmd(cmd)
+ # Add the rx-key to the address
+ cmd += f' sa 0 pn 1 on key 01 {peer_config["key"]}'
+ self._cmd(cmd)
+
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index fe5e9c519..4aac103ec 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2023 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
@@ -25,6 +25,7 @@ from hurry.filesize import alternative
from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
+from vyos.base import Warning
class WireGuardOperational(Operational):
def _dump(self):
@@ -184,7 +185,6 @@ class WireGuardIf(Interface):
base_cmd += f' private-key {tmp_file.name}'
base_cmd = base_cmd.format(**config)
-
if 'peer' in config:
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 418efe649..9cbc2b96e 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -56,10 +56,13 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
elif 'translation' in rule_conf:
addr = dict_search_args(rule_conf, 'translation', 'address')
port = dict_search_args(rule_conf, 'translation', 'port')
- redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port')
- if redirect_port:
- translation_output = [f'redirect to {redirect_port}']
+ if 'redirect' in rule_conf['translation']:
+ translation_output = [f'redirect']
+ redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port')
+ if redirect_port:
+ translation_output.append(f'to {redirect_port}')
else:
+
translation_prefix = nat_type[:1]
translation_output = [f'{translation_prefix}nat']
@@ -94,7 +97,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
if options:
translation_str += f' {",".join(options)}'
- if 'backend' in rule_conf['load_balance']:
+ if not ipv6 and 'backend' in rule_conf['load_balance']:
hash_input_items = []
current_prob = 0
nat_map = []
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index cd15e3878..792e24b76 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2023 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
@@ -63,6 +63,18 @@ private_format_map = {
'OpenSSH': serialization.PrivateFormat.OpenSSH
}
+hash_map = {
+ 'sha256': hashes.SHA256,
+ 'sha384': hashes.SHA384,
+ 'sha512': hashes.SHA512,
+}
+
+def get_certificate_fingerprint(cert, hash):
+ hash_algorithm = hash_map[hash]()
+ fp = cert.fingerprint(hash_algorithm)
+
+ return fp.hex(':').upper()
+
def encode_certificate(cert):
return cert.public_bytes(encoding=serialization.Encoding.PEM).decode('utf-8')
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 6469623fd..e167488c6 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -574,9 +574,9 @@ def nft_action(vyos_action):
return vyos_action
@register_filter('nft_rule')
-def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
+def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'):
from vyos.firewall import parse_rule
- return parse_rule(rule_conf, fw_name, rule_id, ip_name)
+ return parse_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name)
@register_filter('nft_default_rule')
def nft_default_rule(fw_conf, fw_name, ipv6=False):
@@ -587,7 +587,8 @@ def nft_default_rule(fw_conf, fw_name, ipv6=False):
action_suffix = default_action[:1].upper()
output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')
- output.append(nft_action(default_action))
+ #output.append(nft_action(default_action))
+ output.append(f'{default_action}')
if 'default_jump_target' in fw_conf:
target = fw_conf['default_jump_target']
def_suffix = '6' if ipv6 else ''
diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py
index ec2333ef0..9a8a1ff7d 100644
--- a/python/vyos/utils/convert.py
+++ b/python/vyos/utils/convert.py
@@ -144,32 +144,54 @@ def mac_to_eui64(mac, prefix=None):
except: # pylint: disable=bare-except
return
-def convert_data(data):
- """Convert multiple types of data to types usable in CLI
+
+def convert_data(data) -> dict | list | tuple | str | int | float | bool | None:
+ """Filter and convert multiple types of data to types usable in CLI/API
+
+ WARNING: Must not be used for anything except formatting output for API or CLI
+
+ On the output allowed everything supported in JSON.
Args:
- data (str | bytes | list | OrderedDict): input data
+ data (Any): input data
Returns:
- str | list | dict: converted data
+ dict | list | tuple | str | int | float | bool | None: converted data
"""
from base64 import b64encode
- from collections import OrderedDict
- if isinstance(data, str):
+ # return original data for types which do not require conversion
+ if isinstance(data, str | int | float | bool | None):
return data
- if isinstance(data, bytes):
- try:
- return data.decode()
- except UnicodeDecodeError:
- return b64encode(data).decode()
+
if isinstance(data, list):
list_tmp = []
for item in data:
list_tmp.append(convert_data(item))
return list_tmp
- if isinstance(data, OrderedDict):
+
+ if isinstance(data, tuple):
+ list_tmp = list(data)
+ tuple_tmp = tuple(convert_data(list_tmp))
+ return tuple_tmp
+
+ if isinstance(data, bytes | bytearray):
+ try:
+ return data.decode()
+ except UnicodeDecodeError:
+ return b64encode(data).decode()
+
+ if isinstance(data, set | frozenset):
+ list_tmp = convert_data(list(data))
+ return list_tmp
+
+ if isinstance(data, dict):
dict_tmp = {}
for key, value in data.items():
dict_tmp[key] = convert_data(value)
return dict_tmp
+
+ # do not return anything for other types
+ # which cannot be converted to JSON
+ # for example: complex | range | memoryview
+ return
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 3f9a3ef4b..2f181d8d9 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -36,6 +36,10 @@ def get_protocol_by_name(protocol_name):
except socket.error:
return protocol_name
+def interface_exists(interface) -> bool:
+ import os
+ return os.path.exists(f'/sys/class/net/{interface}')
+
def interface_exists_in_netns(interface_name, netns):
from vyos.utils.process import rc_cmd
rc, out = rc_cmd(f'ip netns exec {netns} ip link show dev {interface_name}')
@@ -43,6 +47,24 @@ def interface_exists_in_netns(interface_name, netns):
return True
return False
+def get_vrf_members(vrf: str) -> list:
+ """
+ Get list of interface VRF members
+ :param vrf: str
+ :return: list
+ """
+ import json
+ from vyos.utils.process import cmd
+ if not interface_exists(vrf):
+ raise ValueError(f'VRF "{vrf}" does not exist!')
+ output = cmd(f'ip --json --brief link show master {vrf}')
+ answer = json.loads(output)
+ interfaces = []
+ for data in answer:
+ if 'ifname' in data:
+ interfaces.append(data.get('ifname'))
+ return interfaces
+
def get_interface_vrf(interface):
""" Returns VRF of given interface """
from vyos.utils.dict import dict_search
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index ad2130dca..bf434865d 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -13,8 +13,12 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
+from typing import Optional, Union, TYPE_CHECKING
from vyos.xml_ref import definition
+if TYPE_CHECKING:
+ from vyos.config import ConfigDict
+
def load_reference(cache=[]):
if cache:
return cache[0]
@@ -23,11 +27,15 @@ def load_reference(cache=[]):
try:
from vyos.xml_ref.cache import reference
- xml.define(reference)
- cache.append(xml)
except Exception:
raise ImportError('no xml reference cache !!')
+ if not reference:
+ raise ValueError('empty xml reference cache !!')
+
+ xml.define(reference)
+ cache.append(xml)
+
return xml
def is_tag(path: list) -> bool:
@@ -48,12 +56,12 @@ def is_leaf(path: list) -> bool:
def cli_defined(path: list, node: str, non_local=False) -> bool:
return load_reference().cli_defined(path, node, non_local=non_local)
-def from_source(d: dict, path: list) -> bool:
- return load_reference().from_source(d, path)
-
def component_version() -> dict:
return load_reference().component_version()
+def default_value(path: list) -> Optional[Union[str, list]]:
+ return load_reference().default_value(path)
+
def multi_to_list(rpath: list, conf: dict) -> dict:
return load_reference().multi_to_list(rpath, conf)
@@ -68,8 +76,8 @@ def relative_defaults(rpath: list, conf: dict, get_first_key=False,
get_first_key=get_first_key,
recursive=recursive)
-def merge_defaults(path: list, conf: dict, get_first_key=False,
- recursive=False) -> dict:
- return load_reference().merge_defaults(path, conf,
- get_first_key=get_first_key,
- recursive=recursive)
+def from_source(d: dict, path: list) -> bool:
+ return definition.from_source(d, path)
+
+def ext_dict_merge(source: dict, destination: Union[dict, 'ConfigDict']):
+ return definition.ext_dict_merge(source, destination)
diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py
index d95d580e2..c90c5ddbc 100644
--- a/python/vyos/xml_ref/definition.py
+++ b/python/vyos/xml_ref/definition.py
@@ -20,6 +20,45 @@ from typing import Optional, Union, Any, TYPE_CHECKING
if TYPE_CHECKING:
from vyos.config import ConfigDict
+def set_source_recursive(o: Union[dict, str, list], b: bool):
+ d = {}
+ if not isinstance(o, dict):
+ d = {'_source': b}
+ else:
+ for k, v in o.items():
+ d[k] = set_source_recursive(v, b)
+ d |= {'_source': b}
+ return d
+
+def source_dict_merge(src: dict, dest: dict):
+ from copy import deepcopy
+ dst = deepcopy(dest)
+ from_src = {}
+
+ for key, value in src.items():
+ if key not in dst:
+ dst[key] = value
+ from_src[key] = set_source_recursive(value, True)
+ elif isinstance(src[key], dict):
+ dst[key], f = source_dict_merge(src[key], dst[key])
+ f |= {'_source': False}
+ from_src[key] = f
+
+ return dst, from_src
+
+def ext_dict_merge(src: dict, dest: Union[dict, 'ConfigDict']):
+ d, f = source_dict_merge(src, dest)
+ if hasattr(d, '_from_defaults'):
+ setattr(d, '_from_defaults', f)
+ return d
+
+def from_source(d: dict, path: list) -> bool:
+ for key in path:
+ d = d[key] if key in d else {}
+ if not d or not isinstance(d, dict):
+ return False
+ return d.get('_source', False)
+
class Xml:
def __init__(self):
self.ref = {}
@@ -123,7 +162,7 @@ class Xml:
def component_version(self) -> dict:
d = {}
- for k, v in self.ref['component_version']:
+ for k, v in self.ref['component_version'].items():
d[k] = int(v)
return d
@@ -153,6 +192,15 @@ class Xml:
return default.split()
return default
+ def default_value(self, path: list) -> Optional[Union[str, list]]:
+ d = self._get_ref_path(path)
+ default = self._get_default_value(d)
+ if default is None:
+ return None
+ if self._is_multi_node(d) or self._is_tag_node(d):
+ return default.split()
+ return default
+
def get_defaults(self, path: list, get_first_key=False, recursive=False) -> dict:
"""Return dict containing default values below path
@@ -212,43 +260,6 @@ class Xml:
return False
return True
- def _set_source_recursive(self, o: Union[dict, str, list], b: bool):
- d = {}
- if not isinstance(o, dict):
- d = {'_source': b}
- else:
- for k, v in o.items():
- d[k] = self._set_source_recursive(v, b)
- d |= {'_source': b}
- return d
-
- # use local copy of function in module configdict, to avoid circular
- # import
- #
- # extend dict_merge to keep track of keys only in source
- def _dict_merge(self, source, destination):
- from copy import deepcopy
- dest = deepcopy(destination)
- from_source = {}
-
- for key, value in source.items():
- if key not in dest:
- dest[key] = value
- from_source[key] = self._set_source_recursive(value, True)
- elif isinstance(source[key], dict):
- dest[key], f = self._dict_merge(source[key], dest[key])
- f |= {'_source': False}
- from_source[key] = f
-
- return dest, from_source
-
- def from_source(self, d: dict, path: list) -> bool:
- for key in path:
- d = d[key] if key in d else {}
- if not d or not isinstance(d, dict):
- return False
- return d.get('_source', False)
-
def _relative_defaults(self, rpath: list, conf: dict, recursive=False) -> dict:
res: dict = {}
res = self.get_defaults(rpath, recursive=recursive,
@@ -289,17 +300,3 @@ class Xml:
res = {}
return res
-
- def merge_defaults(self, path: list, conf: Union[dict, 'ConfigDict'],
- get_first_key=False, recursive=False) -> dict:
- """Return config dict with defaults non-destructively merged
-
- This merges non-recursive defaults relative to the config dict.
- """
- d = self.relative_defaults(path, conf, get_first_key=get_first_key,
- recursive=recursive)
- d, f = self._dict_merge(d, conf)
- d = type(conf)(d)
- if hasattr(d, '_from_defaults'):
- setattr(d, '_from_defaults', f)
- return d
diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex
index 909e6d17b..aa9837fe9 100644
--- a/smoketest/configs/dialup-router-complex
+++ b/smoketest/configs/dialup-router-complex
@@ -1094,6 +1094,10 @@ firewall {
adjust-mss 1452
adjust-mss6 1432
}
+ interface eth0.10 {
+ adjust-mss 1320
+ adjust-mss6 1300
+ }
}
receive-redirects disable
send-redirects enable
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index cef61e42b..b5b65e253 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -843,6 +843,7 @@ class BasicInterfaceTest:
mss = '1400'
dad_transmits = '10'
+ accept_dad = '0'
for interface in self._interfaces:
path = self._base_path + [interface]
@@ -853,6 +854,9 @@ class BasicInterfaceTest:
if cli_defined(self._base_path + ['ipv6'], 'adjust-mss'):
self.cli_set(path + ['ipv6', 'adjust-mss', mss])
+ if cli_defined(self._base_path + ['ipv6'], 'accept-dad'):
+ self.cli_set(path + ['ipv6', 'accept-dad', accept_dad])
+
if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'):
self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits])
@@ -870,6 +874,10 @@ class BasicInterfaceTest:
if line.startswith(base_options):
self.assertIn(f'tcp option maxseg size set {mss}', line)
+ if cli_defined(self._base_path + ['ipv6'], 'accept-dad'):
+ tmp = read_file(f'{proc_base}/accept_dad')
+ self.assertEqual(accept_dad, tmp)
+
if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'):
tmp = read_file(f'{proc_base}/dad_transmits')
self.assertEqual(dad_transmits, tmp)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 0c56c2c93..b2076c077 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -90,19 +90,19 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
return False
def test_geoip(self):
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'drop'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match'])
self.cli_commit()
nftables_search = [
- ['ip saddr @GEOIP_CC_smoketest_1', 'drop'],
- ['ip saddr != @GEOIP_CC_smoketest_2', 'return']
+ ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'],
+ ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept']
]
# -t prevents 1000+ GeoIP elements being returned
@@ -127,36 +127,33 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0'])
self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'outbound-interface', 'interface-group', 'smoketest_interface'])
-
- self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'interface-group', '!smoketest_interface'])
self.cli_commit()
self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5')
nftables_search = [
- ['iifname "eth0"', 'jump NAME_smoketest'],
- ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'return'],
+ ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'],
['elements = { 172.16.99.0/24 }'],
['elements = { 53, 123 }'],
- ['ether saddr @M_smoketest_mac', 'return'],
+ ['ether saddr @M_smoketest_mac', 'accept'],
['elements = { 00:01:02:03:04:05 }'],
['set D_smoketest_domain'],
['elements = { 192.0.2.5, 192.0.2.8,'],
['192.0.2.10, 192.0.2.11 }'],
- ['ip saddr @D_smoketest_domain', 'return'],
- ['oifname @I_smoketest_interface', 'return']
+ ['ip saddr @D_smoketest_domain', 'accept'],
+ ['oifname != @I_smoketest_interface', 'accept']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -170,12 +167,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1'])
- self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
-
- self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
self.cli_commit()
@@ -187,8 +182,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1'])
nftables_search = [
- ['iifname "eth0"', 'jump NAME_smoketest'],
- ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'return'],
+ ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'],
['elements = { 172.16.99.0/24, 172.16.101.0/24 }'],
['elements = { 53, 123 }']
]
@@ -198,65 +192,80 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
def test_ipv4_basic_rules(self):
name = 'smoketest'
interface = 'eth0'
+ interface_inv = '!eth0'
interface_wc = 'l2tp*'
mss_range = '501-1460'
conn_mark = '555'
- self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-options', 'level', 'debug'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'ttl', 'eq', '15'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'destination', 'port', '8888'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'log', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-options', 'level', 'err'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'destination', 'port', '22'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'limit', 'rate', '5/minute'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'log', 'disable'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'destination', 'port', '22'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'count', '10'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'time', 'minute'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'packet-type', 'host'])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn'])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'packet-type', 'broadcast'])
- self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', 'interface-name', interface])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', 'interface-name', interface])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'connection-mark', conn_mark])
-
- self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
- self.cli_set(['firewall', 'interface', interface_wc, 'in', 'name', name])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
+
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log', 'disable'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host'])
+
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'interface-name', interface_wc])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark])
+
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'interface-name', interface_inv])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp'])
+ self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark])
self.cli_commit()
mark_hex = "{0:#010x}".format(int(conn_mark))
nftables_search = [
- [f'iifname "{interface}"', f'jump NAME_{name}'],
- [f'iifname "{interface_wc}"', f'jump NAME_{name}'],
- ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" log level debug', 'ip ttl 15', 'return'],
+ ['chain VYOS_FORWARD_filter'],
+ ['type filter hook forward priority filter; policy drop;'],
+ ['tcp dport 22', 'limit rate 5/minute', 'accept'],
+ ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'],
+ ['chain VYOS_INPUT_filter'],
+ ['type filter hook input priority filter; policy accept;'],
+ ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'],
+ ['meta l4proto gre', f'ct mark {mark_hex}', 'return'],
+ ['chain VYOS_OUTPUT_filter'],
+ ['type filter hook output priority filter; policy accept;'],
+ ['meta l4proto gre', f'oifname != "{interface}"', 'drop'],
+ ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'],
+ ['chain NAME_smoketest'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'],
['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
- ['tcp dport 22', 'limit rate 5/minute', 'return'],
- ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'],
- ['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'],
- ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface}"', 'meta pkttype broadcast'],
- ['meta l4proto gre', f'oifname "{interface}"', f'ct mark {mark_hex}', 'return']
+ ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -266,55 +275,54 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
name2 = 'smoketest-adv2'
interface = 'eth0'
- self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'enable-default-log'])
-
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '64'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '512'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'log', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'group', '66'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666'])
- self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000'])
-
- self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000'])
- self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535'])
- self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp', '3-11'])
- self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp-exclude', '21-25'])
-
- self.cli_set(['firewall', 'name', name2, 'default-action', 'jump'])
- self.cli_set(['firewall', 'name', name2, 'default-jump-target', name])
- self.cli_set(['firewall', 'name', name2, 'enable-default-log'])
- self.cli_set(['firewall', 'name', name2, 'rule', '1', 'source', 'address', '198.51.100.1'])
- self.cli_set(['firewall', 'name', name2, 'rule', '1', 'action', 'jump'])
- self.cli_set(['firewall', 'name', name2, 'rule', '1', 'jump-target', name])
-
- self.cli_set(['firewall', 'name', name2, 'rule', '2', 'protocol', 'tcp'])
- self.cli_set(['firewall', 'name', name2, 'rule', '2', 'action', 'queue'])
- self.cli_set(['firewall', 'name', name2, 'rule', '2', 'queue', '3'])
- self.cli_set(['firewall', 'name', name2, 'rule', '3', 'protocol', 'udp'])
- self.cli_set(['firewall', 'name', name2, 'rule', '3', 'action', 'queue'])
- self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue-options', 'fanout'])
- self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue-options', 'bypass'])
- self.cli_set(['firewall', 'name', name2, 'rule', '3', 'queue', '0-15'])
-
- self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
+
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000'])
+
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25'])
+
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
+ self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name])
+
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass'])
+ self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15'])
self.cli_commit()
nftables_search = [
- [f'iifname "{interface}"', f'jump NAME_{name}'],
- ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'return'],
- ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'],
- [f'log prefix "[{name}-default-D]"', 'drop'],
+ ['chain VYOS_FORWARD_filter'],
+ ['type filter hook forward priority filter; policy drop;'],
['ip saddr 198.51.100.1', f'jump NAME_{name}'],
- [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}'],
+ ['chain VYOS_INPUT_filter'],
+ ['type filter hook input priority filter; policy accept;'],
[f'meta l4proto tcp','queue to 3'],
- [f'meta l4proto udp','queue flags bypass,fanout to 0-15']
+ [f'meta l4proto udp','queue flags bypass,fanout to 0-15'],
+ [f'chain NAME_{name}'],
+ ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'],
+ ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'],
+ [f'log prefix "[{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -325,22 +333,20 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1'])
- self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'enable-default-log'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255'])
-
- self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255'])
self.cli_commit()
@@ -357,34 +363,46 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
name = 'v6-smoketest'
interface = 'eth0'
- self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'source', 'address', '2002::1'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log', 'enable'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-options', 'level', 'crit'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log', 'enable'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'interface-name', interface])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'return'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'protocol', 'gre'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', 'interface-name', interface])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return'])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre'])
+ self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'interface-name', interface])
- self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'interface-name', interface])
self.cli_commit()
nftables_search = [
- [f'iifname "{interface}"', f'jump NAME6_{name}'],
- ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" log level crit', 'return'],
+ ['chain VYOS_IPV6_FORWARD_filter'],
+ ['type filter hook forward priority filter; policy accept;'],
['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'],
+ ['chain VYOS_IPV6_INPUT_filter'],
+ ['type filter hook input priority filter; policy accept;'],
+ ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'],
+ ['chain VYOS_IPV6_OUTPUT_filter'],
+ ['type filter hook output priority filter; policy drop;'],
['meta l4proto gre', f'oifname "{interface}"', 'return'],
- ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop']
+ [f'chain NAME6_{name}'],
+ ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" log level crit', 'accept'],
+ [f'"{name} default-action drop"', f'log prefix "[{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -394,40 +412,39 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
name2 = 'v6-smoketest-adv2'
interface = 'eth0'
- self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
-
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '65'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '513'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '1025'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '18'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '53'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length', '1-1999'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length-exclude', '60000-65535'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp', '4-14'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp-exclude', '31-35'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53'])
- self.cli_set(['firewall', 'ipv6-name', name2, 'default-action', 'jump'])
- self.cli_set(['firewall', 'ipv6-name', name2, 'default-jump-target', name])
- self.cli_set(['firewall', 'ipv6-name', name2, 'enable-default-log'])
- self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'source', 'address', '2001:db8::/64'])
- self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'action', 'jump'])
- self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'jump-target', name])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14'])
+ self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35'])
- self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump'])
+ self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name])
self.cli_commit()
nftables_search = [
- [f'iifname "{interface}"', f'jump NAME6_{name}'],
- ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'return'],
- ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'return'],
- [f'log prefix "[{name}-default-D]"', 'drop'],
+ ['chain VYOS_IPV6_FORWARD_filter'],
+ ['type filter hook forward priority filter; policy accept;'],
+ ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'],
+ ['chain VYOS_IPV6_INPUT_filter'],
+ ['type filter hook input priority filter; policy accept;'],
['ip6 saddr 2001:db8::/64', f'jump NAME6_{name}'],
- [f'log prefix "[{name2}-default-J]"', f'jump NAME6_{name}']
+ [f'chain NAME6_{name}'],
+ ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'],
+ [f'log prefix "[{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -438,22 +455,20 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef'])
- self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'accept'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'drop'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
- self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
-
- self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
+ self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
self.cli_commit()
@@ -465,52 +480,32 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
- def test_state_policy(self):
- self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
- self.cli_set(['firewall', 'state-policy', 'related', 'action', 'accept'])
- self.cli_set(['firewall', 'state-policy', 'invalid', 'action', 'drop'])
-
- self.cli_commit()
-
- chains = {
- 'ip vyos_filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'],
- 'ip6 vyos_filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
- }
-
- for table in ['ip vyos_filter', 'ip6 vyos_filter']:
- for chain in chains[table]:
- nftables_output = cmd(f'sudo nft list chain {table} {chain}')
- self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output)
-
def test_ipv4_state_and_status_rules(self):
name = 'smoketest-state'
interface = 'eth0'
- self.cli_set(['firewall', 'name', name, 'default-action', 'drop'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'established', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'related', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject'])
- self.cli_set(['firewall', 'name', name, 'rule', '2', 'state', 'invalid', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'state', 'new', 'enable'])
-
- self.cli_set(['firewall', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'accept'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'new', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'established', 'enable'])
- self.cli_set(['firewall', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source'])
-
- self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new', 'enable'])
+
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established', 'enable'])
+ self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source'])
self.cli_commit()
nftables_search = [
- [f'iifname "{interface}"', f'jump NAME_{name}'],
- ['ct state { established, related }', 'return'],
+ ['ct state { established, related }', 'accept'],
['ct state invalid', 'reject'],
- ['ct state new', 'ct status dnat', 'return'],
- ['ct state { established, new }', 'ct status snat', 'return'],
+ ['ct state new', 'ct status dnat', 'accept'],
+ ['ct state { established, new }', 'ct status snat', 'accept'],
['drop', f'comment "{name} default-action drop"']
]
@@ -523,7 +518,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
with open(path, 'r') as f:
self.assertEqual(f.read().strip(), conf['default'], msg=path)
- self.cli_set(['firewall', name.replace("_", "-"), conf['test_value']])
+ self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']])
self.cli_commit()
@@ -533,35 +528,5 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
with open(path, 'r') as f:
self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
- def test_zone_basic(self):
- self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
- self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
- self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
- self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
- self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest'])
-
- self.cli_commit()
-
- nftables_search = [
- ['chain VZONE_smoketest-eth0'],
- ['chain VZONE_smoketest-local_IN'],
- ['chain VZONE_smoketest-local_OUT'],
- ['oifname "eth0"', 'jump VZONE_smoketest-eth0'],
- ['jump VZONE_smoketest-local_IN'],
- ['jump VZONE_smoketest-local_OUT'],
- ['iifname "eth0"', 'jump NAME_smoketest'],
- ['oifname "eth0"', 'jump NAME_smoketest']
- ]
-
- nftables_output = cmd('sudo nft list table ip vyos_filter')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(matched)
-
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index b32a6f524..30d1ad659 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -208,5 +208,77 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_macsec_static_keys(self):
+ src_interface = 'eth0'
+ interface = 'macsec5'
+ cipher1 = 'gcm-aes-128'
+ cipher2 = 'gcm-aes-256'
+ tx_key_1 = '71a82a48eddfa12c08a19792ca20c4bb'
+ tx_key_2 = 'dd487b2958e855ea35a5d43a5ecb3dcfbe7889ffcb877770252feb13b734478d'
+ rx_key_1 = '0022d00f57e75241a230cdf7118dfcc5'
+ rx_key_2 = 'b7d6d7ad075e02323fdeb845217b884d3f93ff36b2cdaf6b07eeb189b877245f'
+ peer_mac = '00:11:22:33:44:55'
+ self.cli_set(self._base_path + [interface])
+
+ # Encrypt link
+ self.cli_set(self._base_path + [interface, 'security', 'encrypt'])
+
+ # check validate() - source interface is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'source-interface', src_interface])
+
+ # check validate() - cipher is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher1])
+
+ # check validate() - only static or mka config is allowed
+ self.cli_set(self._base_path + [interface, 'security', 'static'])
+ self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', tx_key_1])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(self._base_path + [interface, 'security', 'mka'])
+
+ # check validate() - tx-key required
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # check validate() - tx-key length must match cipher
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_1])
+
+ # check validate() - at least one peer must be defined
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # check validate() - enabled peer must have both rx-key and MAC defined
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac'])
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_1])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac])
+
+ # check validate() - peer rx-key length must match cipher
+ self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher2])
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_2])
+
+ # final commit and verify
+ self.cli_commit()
+ self.assertIn(interface, interfaces())
+ self.assertEqual(cipher2, get_cipher(interface))
+ self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}'))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 5c54f58a3..d1ece84d6 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -337,10 +337,6 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['protocol'])
-
- # check validate() - must specify "tls dh-params" when "tls role" is "passive"
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
self.cli_commit()
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index f84ce159d..48c7cb6a1 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 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
@@ -19,6 +19,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.utils.file import read_file
base_path = ['interfaces', 'wireguard']
@@ -35,7 +36,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def test_wireguard_peer(self):
+ def test_01_wireguard_peer(self):
# Create WireGuard interfaces with associated peers
for intf in self._interfaces:
peer = 'foo-' + intf
@@ -62,7 +63,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}'))
- def test_wireguard_add_remove_peer(self):
+ def test_02_wireguard_add_remove_peer(self):
# T2939: Create WireGuard interfaces with associated peers.
# Remove one of the configured peers.
# T4774: Test prevention of duplicate peer public keys
@@ -100,10 +101,9 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path + [interface, 'peer', 'PEER01'])
self.cli_commit()
- def test_wireguard_same_public_key(self):
- # T2939: Create WireGuard interfaces with associated peers.
- # Remove one of the configured peers.
- # T4774: Test prevention of duplicate peer public keys
+ def test_03_wireguard_same_public_key(self):
+ # T5413: Test prevention of equality interface public key and peer's
+ # public key
interface = 'wg0'
port = '12345'
privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ='
@@ -129,5 +129,28 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}'))
+ def test_04_wireguard_threaded(self):
+ # T5409: Test adding threaded option on interface.
+ # Test prevention for adding threaded
+ # if no enabled peer is configured.
+ interface = 'wg0'
+ port = '12345'
+ privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ='
+ pubkey = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I='
+
+ self.cli_set(base_path + [interface, 'address', '172.16.0.1/24'])
+ self.cli_set(base_path + [interface, 'private-key', privkey])
+
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32'])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1'])
+ self.cli_set(base_path + [interface, 'per-client-thread'])
+
+ # Commit peers
+ self.cli_commit()
+ tmp = read_file(f'/sys/class/net/{interface}/threaded')
+ self.assertTrue(tmp, "1")
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 875ca9dc6..f8686edd8 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -234,9 +234,51 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.assertIn(interface, bridge_members)
self.cli_delete(bridge_path)
- self.cli_delete(self._base_path)
+
+ def test_wireless_security_station_address(self):
+ interface = 'wlan0'
+ ssid = 'VyOS-ACL'
+
+ hostapd_accept_station_conf = f'/run/hostapd/{interface}_station_accept.conf'
+ hostapd_deny_station_conf = f'/run/hostapd/{interface}_station_deny.conf'
+
+ accept_mac = ['00:00:00:00:ac:01', '00:00:00:00:ac:02', '00:00:00:00:ac:03', '00:00:00:00:ac:04']
+ deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04']
+
+ self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'country-code', 'se'])
+ self.cli_set(self._base_path + [interface, 'type', 'access-point'])
+ self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept'])
+
+ for mac in accept_mac:
+ self.cli_set(self._base_path + [interface, 'security', 'station-address', 'accept', 'mac', mac])
+ for mac in deny_mac:
+ self.cli_set(self._base_path + [interface, 'security', 'station-address', 'deny', 'mac', mac])
+
self.cli_commit()
+ # in accept mode all addresses are allowed unless specified in the deny list
+ tmp = get_config_value(interface, 'macaddr_acl')
+ self.assertEqual(tmp, '0')
+
+ accept_list = read_file(hostapd_accept_station_conf)
+ for mac in accept_mac:
+ self.assertIn(mac, accept_list)
+
+ deny_list = read_file(hostapd_deny_station_conf)
+ for mac in deny_mac:
+ self.assertIn(mac, deny_list)
+
+ # Switch mode accept -> deny
+ self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'deny'])
+ self.cli_commit()
+ # In deny mode all addresses are denied unless specified in the allow list
+ tmp = get_config_value(interface, 'macaddr_acl')
+ self.assertEqual(tmp, '1')
+
+ # Check for running process
+ self.assertTrue(process_named_running('hostapd'))
+
if __name__ == '__main__':
check_kmod('mac80211_hwsim')
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index e6eaedeff..31dfcef87 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -244,10 +244,17 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname])
self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port])
+ self.cli_set(dst_path + ['rule', '20', 'destination', 'address', dst_addr_1])
+ self.cli_set(dst_path + ['rule', '20', 'destination', 'port', dest_port])
+ self.cli_set(dst_path + ['rule', '20', 'protocol', protocol])
+ self.cli_set(dst_path + ['rule', '20', 'inbound-interface', ifname])
+ self.cli_set(dst_path + ['rule', '20', 'translation', 'redirect'])
+
self.cli_commit()
nftables_search = [
- [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}']
+ [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}'],
+ [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect']
]
self.verify_nftables(nftables_search, 'ip vyos_nat')
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 5b247a413..77952d8d9 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -868,6 +868,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['address-family', afi, 'export', 'vpn'])
self.cli_set(base_path + ['address-family', afi, 'import', 'vpn'])
self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'export', label])
+ self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'allocation-mode', 'per-nexthop'])
self.cli_set(base_path + ['address-family', afi, 'rd', 'vpn', 'export', rd])
self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'export', route_map_out])
self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'import', route_map_in])
@@ -887,6 +888,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' export vpn', afi_config)
self.assertIn(f' import vpn', afi_config)
self.assertIn(f' label vpn export {label}', afi_config)
+ self.assertIn(f' label vpn export allocation-mode per-nexthop', afi_config)
self.assertIn(f' rd vpn export {rd}', afi_config)
self.assertIn(f' route-map vpn export {route_map_out}', afi_config)
self.assertIn(f' route-map vpn import {route_map_in}', afi_config)
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 511a5eb8b..5ab7fae14 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -295,9 +295,10 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown])
- # Commit interface changes for holddown
- self.cli_commit()
+ # Commit interface changes for holddown
+ self.cli_commit()
+ for interface in self._interfaces:
# Verify interface changes for holddown
tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
self.assertIn(f'interface {interface}', tmp)
@@ -308,9 +309,10 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable'])
- # Commit interface changes for disable
- self.cli_commit()
+ # Commit interface changes for disable
+ self.cli_commit()
+ for interface in self._interfaces:
# Verify interface changes for disable
tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd')
self.assertIn(f'interface {interface}', tmp)
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 80befbfd6..a6850db71 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -56,7 +56,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
@@ -84,7 +84,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' compatible rfc1583', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig)
@@ -116,7 +116,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
for ptotocol in protocols:
@@ -137,7 +137,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -147,7 +147,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -189,7 +189,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' mpls-te on', frrconfig)
self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default
@@ -212,7 +212,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area])
self.cli_commit()
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig)
@@ -228,7 +228,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
for neighbor in neighbors:
self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default
@@ -247,7 +247,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
for protocol in redistribute:
self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -274,7 +274,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' area {area} shortcut {shortcut}', frrconfig)
self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig)
@@ -306,11 +306,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' passive-interface default', frrconfig)
for interface in interfaces:
+ # Can not use daemon for getFRRconfig() as bandwidth parameter belongs to zebra process
config = self.getFRRconfig(f'interface {interface}')
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf authentication-key {password}', config)
@@ -323,6 +324,17 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' no ip ospf passive', config)
self.assertIn(f' bandwidth {bandwidth}', config)
+ # T5467: Remove interface from OSPF process and VRF
+ self.cli_delete(base_path + ['interface'])
+ self.cli_commit()
+
+ for interface in interfaces:
+ # T5467: It must also be removed from FRR config
+ frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ self.assertNotIn(f'interface {interface}', frrconfig)
+ # There should be no OSPF related command at all under the interface
+ self.assertNotIn(f' ip ospf', frrconfig)
+
def test_ospf_11_interface_area(self):
area = '0'
interfaces = Section.interfaces('ethernet')
@@ -339,11 +351,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
for interface in interfaces:
- config = self.getFRRconfig(f'interface {interface}')
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf area {area}', config)
@@ -355,8 +367,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
vrf = 'blue'
vrf_base = ['vrf', 'name', vrf]
vrf_iface = 'eth1'
+ area = '1'
+
self.cli_set(vrf_base + ['table', table])
- self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface])
+ self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface, 'area', area])
self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf])
# Also set a default VRF OSPF config
@@ -364,16 +378,31 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
- frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}')
+ frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME)
self.assertIn(f'router ospf vrf {vrf}', frrconfig)
self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ self.assertIn(f'interface {vrf_iface}', frrconfig)
+ self.assertIn(f' ip ospf area {area}', frrconfig)
+
+ # T5467: Remove interface from OSPF process and VRF
+ self.cli_delete(vrf_base + ['protocols', 'ospf', 'interface'])
+ self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
+ self.cli_commit()
+
+ # T5467: It must also be removed from FRR config
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ self.assertNotIn(f'interface {vrf_iface}', frrconfig)
+ # There should be no OSPF related command at all under the interface
+ self.assertNotIn(f' ip ospf', frrconfig)
+
# cleanup
self.cli_delete(['vrf', 'name', vrf])
self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
@@ -385,7 +414,6 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
area = '0.0.0.10'
network = '10.0.0.0/8'
-
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any'])
self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any'])
@@ -396,7 +424,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default
self.assertIn(f' network {network} area {area}', frrconfig)
@@ -430,7 +458,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify all changes
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f' segment-routing on', frrconfig)
self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig)
self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig)
@@ -449,7 +477,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify main OSPF changes
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig)
self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig)
@@ -457,11 +485,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for interface in interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown])
- # Commit interface changes for holddown
- self.cli_commit()
+ # Commit interface changes for holddown
+ self.cli_commit()
+ for interface in interfaces:
# Verify interface changes for holddown
- config = self.getFRRconfig(f'interface {interface}')
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertIn(f' ip ospf mpls ldp-sync', config)
@@ -470,11 +499,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for interface in interfaces:
self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable'])
- # Commit interface changes for disable
- self.cli_commit()
+ # Commit interface changes for disable
+ self.cli_commit()
+ for interface in interfaces:
# Verify interface changes for disable
- config = self.getFRRconfig(f'interface {interface}')
+ config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', config)
self.assertIn(f' ip ospf dead-interval 40', config)
self.assertIn(f' no ip ospf mpls ldp-sync', config)
@@ -496,7 +526,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf')
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
self.assertIn(f' capability opaque', frrconfig)
self.assertIn(f' graceful-restart grace-period {period}', frrconfig)
diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py
index 64dfa18db..0d6c6c691 100755
--- a/smoketest/scripts/cli/test_protocols_ospfv3.py
+++ b/smoketest/scripts/cli/test_protocols_ospfv3.py
@@ -74,7 +74,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {default_area} range {prefix}', frrconfig)
self.assertIn(f' ospf6 router-id {router_id}', frrconfig)
@@ -82,7 +82,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig)
for interface in interfaces:
- if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d')
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'ipv6 ospf6 area {default_area}', if_config)
self.cli_delete(['policy', 'access-list6', acl_name])
@@ -103,7 +103,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' distance {dist_global}', frrconfig)
self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig)
@@ -123,7 +123,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
for protocol in redistribute:
self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig)
@@ -154,13 +154,13 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
cost = '100'
priority = '10'
for interface in interfaces:
- if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d')
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {interface}', if_config)
self.assertIn(f' ipv6 ospf6 bfd', if_config)
self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config)
@@ -172,6 +172,15 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
cost = str(int(cost) + 10)
priority = str(int(priority) + 5)
+ # Cleanup interfaces
+ self.cli_delete(base_path + ['interface'])
+ self.cli_commit()
+
+ for interface in interfaces:
+ if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME)
+ # There should be no OSPF6 configuration at all after interface removal
+ self.assertNotIn(f' ipv6 ospf6', if_config)
+
def test_ospfv3_05_area_stub(self):
area_stub = '23'
@@ -184,7 +193,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {area_stub} stub', frrconfig)
self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig)
@@ -210,7 +219,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' area {area_nssa} nssa', frrconfig)
self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig)
@@ -230,7 +239,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -239,7 +248,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
@@ -265,18 +274,29 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' ospf6 router-id {router_id}', frrconfig)
- frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon='ospf6d')
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
self.assertIn(f'interface {vrf_iface}', frrconfig)
self.assertIn(f' ipv6 ospf6 bfd', frrconfig)
- frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon='ospf6d')
+ frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6 vrf {vrf}', frrconfig)
self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig)
+ # T5467: Remove interface from OSPF process and VRF
+ self.cli_delete(vrf_base + ['protocols', 'ospfv3', 'interface'])
+ self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
+ self.cli_commit()
+
+ # T5467: It must also be removed from FRR config
+ frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME)
+ self.assertNotIn(f'interface {vrf_iface}', frrconfig)
+ # There should be no OSPF related command at all under the interface
+ self.assertNotIn(f' ipv6 ospf6', frrconfig)
+
# cleanup
self.cli_delete(['vrf', 'name', vrf])
self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf'])
@@ -298,7 +318,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Verify FRR ospfd configuration
- frrconfig = self.getFRRconfig('router ospf6')
+ frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
self.assertIn(f' graceful-restart grace-period {period}', frrconfig)
self.assertIn(f' graceful-restart helper planned-only', frrconfig)
diff --git a/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py
new file mode 100755
index 000000000..cb5f84406
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
+from vyos.utils.process import process_named_running
+from vyos.utils.file import read_file
+
+
+PROCESS_NAME = 'zabbix_agent2'
+ZABBIX_AGENT_CONF = '/run/zabbix/zabbix-agent2.conf'
+base_path = ['service', 'monitoring', 'zabbix-agent']
+
+
+class TestZabbixAgent(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Process must be terminated after deleting the config
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_01_zabbix_agent(self):
+ directory = '/tmp'
+ buffer_send = '8'
+ buffer_size = '120'
+ log_level = {'warning': '3'}
+ log_size = '1'
+ servers = ['192.0.2.1', '2001:db8::1']
+ servers_active = {'192.0.2.5': {'port': '10051'}, '2001:db8::123': {'port': '10052'}}
+ port = '10050'
+ timeout = '5'
+ listen_ip = '0.0.0.0'
+ hostname = 'r-vyos'
+
+ self.cli_set(base_path + ['directory', directory])
+ self.cli_set(base_path + ['limits', 'buffer-flush-interval', buffer_send])
+ self.cli_set(base_path + ['limits', 'buffer-size', buffer_size])
+ self.cli_set(base_path + ['log', 'debug-level', next(iter(log_level))])
+ self.cli_set(base_path + ['log', 'size', log_size])
+ for server in servers:
+ self.cli_set(base_path + ['server', server])
+ for server_active, server_config in servers_active.items():
+ self.cli_set(base_path + ['server-active', server_active, 'port', server_config['port']])
+ self.cli_set(base_path + ['timeout', timeout])
+ self.cli_set(base_path + ['host-name', hostname])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(ZABBIX_AGENT_CONF)
+
+ self.assertIn(f'LogFileSize={log_size}', config)
+ self.assertIn(f'DebugLevel={log_level.get("warning")}', config)
+
+ self.assertIn(f'Server={",".join(sorted(servers))}', config)
+ tmp = 'ServerActive=192.0.2.5:10051,[2001:db8::123]:10052'
+ self.assertIn(tmp, config)
+
+ self.assertIn(f'ListenPort={port}', config)
+ self.assertIn(f'ListenIP={listen_ip}', config)
+ self.assertIn(f'BufferSend={buffer_send}', config)
+ self.assertIn(f'BufferSize={buffer_size}', config)
+ self.assertIn(f'Include={directory}/*.conf', config)
+ self.assertIn(f'Timeout={timeout}', config)
+ self.assertIn(f'Hostname={hostname}', config)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 2a77540f7..9c43640a9 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -20,7 +20,6 @@ import re
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.firewall import find_nftables_rule
from vyos.firewall import remove_nftables_rule
from vyos.utils.process import process_named_running
@@ -28,7 +27,6 @@ from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -77,16 +75,8 @@ def get_config(config=None):
base = ['system', 'conntrack']
conntrack = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
-
- # 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)
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if 'timeout' in default_values and 'custom' in default_values['timeout']:
- del default_values['timeout']['custom']
- conntrack = dict_merge(default_values, conntrack)
+ get_first_key=True,
+ with_recursive_defaults=True)
return conntrack
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
index 6a4d102f7..4fb2ce27f 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -18,7 +18,6 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configverify import verify_interface_exists
from vyos.utils.dict import dict_search
from vyos.utils.process import process_named_running
@@ -28,7 +27,6 @@ from vyos.utils.process import run
from vyos.template import render
from vyos.template import get_ipv4
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -50,11 +48,7 @@ def get_config(config=None):
return None
conntrack = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True)
- # 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)
- conntrack = dict_merge(default_values, conntrack)
+ get_first_key=True, with_defaults=True)
conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize')
conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max')
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 3378aac63..478868a9a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -33,11 +33,12 @@ from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.utils.process import rc_cmd
+from vyos.template import bracketize_ipv6
from vyos.template import inc_ip
from vyos.template import is_ipv4
from vyos.template import is_ipv6
from vyos.template import render
-from vyos.xml import defaults
+from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -66,58 +67,26 @@ def get_config(config=None):
base = ['container']
container = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
- # 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)
- # container base default values can not be merged here - remove and add them later
- if 'name' in default_values:
- del default_values['name']
- # registry will be handled below
- if 'registry' in default_values:
- del default_values['registry']
- container = dict_merge(default_values, container)
-
- # Merge per-container default values
- if 'name' in container:
- default_values = defaults(base + ['name'])
- if 'port' in default_values:
- del default_values['port']
- if 'volume' in default_values:
- del default_values['volume']
- for name in container['name']:
- container['name'][name] = dict_merge(default_values, container['name'][name])
-
- # T5047: Any container related configuration changed? We only
- # wan't to restart the required containers and not all of them ...
- tmp = is_node_changed(conf, base + ['name', name])
- if tmp:
- if 'container_restart' not in container:
- container['container_restart'] = [name]
- else:
- container['container_restart'].append(name)
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if 'port' in container['name'][name]:
- for port in container['name'][name]['port']:
- default_values_port = defaults(base + ['name', 'port'])
- container['name'][name]['port'][port] = dict_merge(
- default_values_port, container['name'][name]['port'][port])
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if 'volume' in container['name'][name]:
- for volume in container['name'][name]['volume']:
- default_values_volume = defaults(base + ['name', 'volume'])
- container['name'][name]['volume'][volume] = dict_merge(
- default_values_volume, container['name'][name]['volume'][volume])
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
+
+ for name in container.get('name', []):
+ # T5047: Any container related configuration changed? We only
+ # wan't to restart the required containers and not all of them ...
+ tmp = is_node_changed(conf, base + ['name', name])
+ if tmp:
+ if 'container_restart' not in container:
+ container['container_restart'] = [name]
+ else:
+ container['container_restart'].append(name)
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
container.update({'registry' : {}})
- default_values = defaults(base)
- for registry in default_values['registry'].split():
+ default_values = default_value(base + ['registry'])
+ for registry in default_values:
tmp = {registry : {}}
container['registry'] = dict_merge(tmp, container['registry'])
@@ -312,6 +281,14 @@ def generate_run_arguments(name, container_config):
protocol = container_config['port'][portmap]['protocol']
sport = container_config['port'][portmap]['source']
dport = container_config['port'][portmap]['destination']
+ listen_addresses = container_config['port'][portmap].get('listen_address', [])
+
+ # If listen_addresses is not empty, include them in the publish command
+ if listen_addresses:
+ for listen_address in listen_addresses:
+ port += f' --publish {bracketize_ipv6(listen_address)}:{sport}:{dport}/{protocol}'
+ else:
+ # If listen_addresses is empty, just include the standard publish command
port += f' --publish {sport}:{dport}/{protocol}'
# Bind volume
diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py
index fd39bd9fe..37d708847 100755
--- a/src/conf_mode/dhcp_relay.py
+++ b/src/conf_mode/dhcp_relay.py
@@ -20,12 +20,10 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.base import Warning
from vyos.utils.process import call
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -41,11 +39,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- relay = dict_merge(default_values, relay)
+ relay = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return relay
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 3ea708902..c4c72aae9 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -23,14 +23,12 @@ from netaddr import IPRange
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.process import call
from vyos.utils.process import run
from vyos.utils.network import is_subnet_connected
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -109,19 +107,15 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
- # T2665: defaults include lease time per TAG node which need to be added to
- # individual subnet definitions
- default_values = defaults(base + ['shared-network-name', 'subnet'])
+ dhcp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
if 'shared_network_name' in dhcp:
for network, network_config in dhcp['shared_network_name'].items():
if 'subnet' in network_config:
for subnet, subnet_config in network_config['subnet'].items():
- if 'lease' not in subnet_config:
- dhcp['shared_network_name'][network]['subnet'][subnet] = dict_merge(
- default_values, dhcp['shared_network_name'][network]['subnet'][subnet])
-
# If exclude IP addresses are defined we need to slice them out of
# the defined ranges
if {'exclude', 'range'} <= set(subnet_config):
@@ -302,6 +296,10 @@ def generate(dhcp):
render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
+ # Clean up configuration test file
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+
return None
def apply(dhcp):
diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py
index d912611b3..6537ca3c2 100755
--- a/src/conf_mode/dhcpv6_relay.py
+++ b/src/conf_mode/dhcpv6_relay.py
@@ -19,14 +19,11 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import is_ipv6
from vyos.utils.process import call
-from vyos.utils.dict import dict_search
from vyos.utils.network import is_ipv6_link_local
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -42,11 +39,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- relay = dict_merge(default_values, relay)
+ relay = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return relay
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index 97d46148a..ab80defe8 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -19,10 +19,8 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -50,8 +48,9 @@ def get_config(config=None):
return None
dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True,
- with_defaults=True, with_recursive_defaults=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
dyndns['config_file'] = config_file
return dyndns
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 2d98bffe3..c186f47af 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -21,14 +21,12 @@ from sys import exit
from glob import glob
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.hostsd_client import Client as hostsd_client
from vyos.template import render
from vyos.template import bracketize_ipv6
from vyos.utils.process import call
from vyos.utils.permission import chown
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -52,31 +50,10 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrieved.
- default_values = defaults(base)
- # T2665 due to how defaults under tag nodes work, we must clear these out before we merge
- del default_values['authoritative_domain']
- del default_values['name_server']
- del default_values['domain']['name_server']
- dns = dict_merge(default_values, dns)
-
- # T2665: we cleared default values for tag node 'name_server' above.
- # We now need to add them back back in a granular way.
- if 'name_server' in dns:
- default_values = defaults(base + ['name-server'])
- for server in dns['name_server']:
- dns['name_server'][server] = dict_merge(default_values, dns['name_server'][server])
-
- # T2665: we cleared default values for tag node 'domain' above.
- # We now need to add them back back in a granular way.
- if 'domain' in dns:
- default_values = defaults(base + ['domain', 'name-server'])
- for domain in dns['domain'].keys():
- for server in dns['domain'][domain]['name_server']:
- dns['domain'][domain]['name_server'][server] = dict_merge(
- default_values, dns['domain'][domain]['name_server'][server])
+ dns = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
# some additions to the default dictionary
if 'system' in dns:
@@ -109,9 +86,6 @@ def get_config(config=None):
rdata = recorddata[rtype][subnode]
if rtype in [ 'a', 'aaaa' ]:
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- rdata = dict_merge(rdefaults, rdata)
-
if not 'address' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required')
continue
@@ -127,9 +101,6 @@ def get_config(config=None):
'value': address
})
elif rtype in ['cname', 'ptr', 'ns']:
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- rdata = dict_merge(rdefaults, rdata)
-
if not 'target' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required')
continue
@@ -141,18 +112,12 @@ def get_config(config=None):
'value': '{}.'.format(rdata['target'])
})
elif rtype == 'mx':
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- del rdefaults['server']
- rdata = dict_merge(rdefaults, rdata)
-
if not 'server' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required')
continue
for servername in rdata['server']:
serverdata = rdata['server'][servername]
- serverdefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'server']) # T2665
- serverdata = dict_merge(serverdefaults, serverdata)
zone['records'].append({
'name': subnode,
'type': rtype.upper(),
@@ -160,9 +125,6 @@ def get_config(config=None):
'value': '{} {}.'.format(serverdata['priority'], servername)
})
elif rtype == 'txt':
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- rdata = dict_merge(rdefaults, rdata)
-
if not 'value' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required')
continue
@@ -175,9 +137,6 @@ def get_config(config=None):
'value': "\"{}\"".format(value.replace("\"", "\\\""))
})
elif rtype == 'spf':
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- rdata = dict_merge(rdefaults, rdata)
-
if not 'value' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required')
continue
@@ -189,19 +148,12 @@ def get_config(config=None):
'value': '"{}"'.format(rdata['value'].replace("\"", "\\\""))
})
elif rtype == 'srv':
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- del rdefaults['entry']
- rdata = dict_merge(rdefaults, rdata)
-
if not 'entry' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required')
continue
for entryno in rdata['entry']:
entrydata = rdata['entry'][entryno]
- entrydefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'entry']) # T2665
- entrydata = dict_merge(entrydefaults, entrydata)
-
if not 'hostname' in entrydata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}')
continue
@@ -217,19 +169,12 @@ def get_config(config=None):
'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname'])
})
elif rtype == 'naptr':
- rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665
- del rdefaults['rule']
- rdata = dict_merge(rdefaults, rdata)
-
-
if not 'rule' in rdata:
dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required')
continue
for ruleno in rdata['rule']:
ruledata = rdata['rule'][ruleno]
- ruledefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'rule']) # T2665
- ruledata = dict_merge(ruledefaults, ruledata)
flags = ""
if 'lookup-srv' in ruledata:
flags += "S"
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 07166d457..8ad3f27fc 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -23,7 +23,6 @@ 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
from vyos.configdiff import get_config_diff, Diff
from vyos.configdep import set_dependents, call_dependents
@@ -37,7 +36,6 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import process_named_running
from vyos.utils.process import rc_cmd
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -97,19 +95,22 @@ def geoip_updated(conf, firewall):
updated = False
for key, path in dict_search_recursive(firewall, 'geoip'):
- set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
- if path[0] == 'name':
+ set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
+ if (path[0] == 'ipv4'):
out['name'].append(set_name)
- elif path[0] == 'ipv6_name':
+ elif (path[0] == 'ipv6'):
+ set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
out['ipv6_name'].append(set_name)
+
updated = True
if 'delete' in node_diff:
for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
- set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
- if path[0] == 'name':
+ set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
+ if (path[0] == 'ipv4'):
out['deleted_name'].append(set_name)
- elif path[0] == 'ipv6-name':
+ elif (path[0] == 'ipv6'):
+ set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
out['deleted_ipv6_name'].append(set_name)
updated = True
@@ -125,54 +126,17 @@ def get_config(config=None):
conf = Config()
base = ['firewall']
- firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # We have gathered the dict representation of the CLI, but there are
- # default options which we need to update into the dictionary retrived.
- # XXX: T2665: we currently have no nice way for defaults under tag
- # nodes, thus we load the defaults "by hand"
- default_values = defaults(base)
- for tmp in ['name', 'ipv6_name']:
- if tmp in default_values:
- del default_values[tmp]
-
- if 'zone' in default_values:
- del default_values['zone']
-
- firewall = dict_merge(default_values, firewall)
-
- # Merge in defaults for IPv4 ruleset
- if 'name' in firewall:
- default_values = defaults(base + ['name'])
- for name in firewall['name']:
- firewall['name'][name] = dict_merge(default_values,
- firewall['name'][name])
-
- # Merge in defaults for IPv6 ruleset
- if 'ipv6_name' in firewall:
- default_values = defaults(base + ['ipv6-name'])
- for ipv6_name in firewall['ipv6_name']:
- firewall['ipv6_name'][ipv6_name] = dict_merge(default_values,
- firewall['ipv6_name'][ipv6_name])
-
- if 'zone' in firewall:
- default_values = defaults(base + ['zone'])
- for zone in firewall['zone']:
- firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone])
+ firewall = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
+
firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
if firewall['group_resync']:
# Update nat and policy-route as firewall groups were updated
set_dependents('group_resync', conf)
- if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
- diff = get_config_diff(conf)
- firewall['trap_diff'] = diff.get_child_nodes_diff_str(base)
- firewall['trap_targets'] = conf.get_config_dict(['service', 'snmp', 'trap-target'],
- key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
firewall['geoip_updated'] = geoip_updated(conf, firewall)
fqdn_config_parse(firewall)
@@ -191,11 +155,11 @@ def verify_rule(firewall, rule_conf, ipv6):
raise ConfigError('jump-target defined, but action jump needed and it is not defined')
target = rule_conf['jump_target']
if not ipv6:
- if target not in dict_search_args(firewall, 'name'):
+ if target not in dict_search_args(firewall, 'ipv4', 'name'):
raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
else:
- if target not in dict_search_args(firewall, 'ipv6_name'):
- raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system')
+ if target not in dict_search_args(firewall, 'ipv6', 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
if 'queue_options' in rule_conf:
if 'queue' not in rule_conf['action']:
@@ -312,10 +276,6 @@ def verify_nested_group(group_name, group, groups, seen):
verify_nested_group(g, groups[g], groups, seen)
def verify(firewall):
- if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
- if not firewall['trap_targets']:
- raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined')
-
if 'group' in firewall:
for group_type in nested_group_types:
if group_type in firewall['group']:
@@ -323,95 +283,45 @@ def verify(firewall):
for group_name, group in groups.items():
verify_nested_group(group_name, group, groups, [])
- for name in ['name', 'ipv6_name']:
- if name in firewall:
- for name_id, name_conf in firewall[name].items():
- if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
- raise ConfigError('default-action set to jump, but no default-jump-target specified')
- if 'default_jump_target' in name_conf:
- target = name_conf['default_jump_target']
- if 'jump' not in name_conf['default_action']:
- raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined')
- if name_conf['default_jump_target'] == name_id:
- raise ConfigError(f'Loop detected on default-jump-target.')
- ## Now need to check that default-jump-target exists (other firewall chain/name)
- if target not in dict_search_args(firewall, name):
- raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system')
-
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
- verify_rule(firewall, rule_conf, name == 'ipv6_name')
-
- if 'interface' in firewall:
- for ifname, if_firewall in firewall['interface'].items():
- # verify ifname needs to be disabled, dynamic devices come up later
- # verify_interface_exists(ifname)
-
- for direction in ['in', 'out', 'local']:
- name = dict_search_args(if_firewall, direction, 'name')
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
-
- if name and dict_search_args(firewall, 'name', name) == None:
- raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}')
-
- if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
- raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}')
-
- local_zone = False
- zone_interfaces = []
-
- if 'zone' in firewall:
- for zone, zone_conf in firewall['zone'].items():
- if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
- raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
-
- if 'local_zone' in zone_conf:
- if local_zone:
- raise ConfigError('There cannot be multiple local zones')
- if 'interface' in zone_conf:
- raise ConfigError('Local zone cannot have interfaces assigned')
- if 'intra_zone_filtering' in zone_conf:
- raise ConfigError('Local zone cannot use intra-zone-filtering')
- local_zone = True
-
- if 'interface' in zone_conf:
- found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
-
- if found_duplicates:
- raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
-
- zone_interfaces += zone_conf['interface']
-
- if 'intra_zone_filtering' in zone_conf:
- intra_zone = zone_conf['intra_zone_filtering']
-
- if len(intra_zone) > 1:
- raise ConfigError('Only one intra-zone-filtering action must be specified')
-
- if 'firewall' in intra_zone:
- v4_name = dict_search_args(intra_zone, 'firewall', 'name')
- if v4_name and not dict_search_args(firewall, 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name')
- if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not v4_name and not v6_name:
- raise ConfigError('No firewall names specified for intra-zone-filtering')
-
- if 'from' in zone_conf:
- for from_zone, from_conf in zone_conf['from'].items():
- if from_zone not in firewall['zone']:
- raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
-
- v4_name = dict_search_args(from_conf, 'firewall', 'name')
- if v4_name and not dict_search_args(firewall, 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
- if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+ if 'ipv4' in firewall:
+ for name in ['name','forward','input','output']:
+ if name in firewall['ipv4']:
+ for name_id, name_conf in firewall['ipv4'][name].items():
+ if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
+ raise ConfigError('default-action set to jump, but no default-jump-target specified')
+ if 'default_jump_target' in name_conf:
+ target = name_conf['default_jump_target']
+ if 'jump' not in name_conf['default_action']:
+ raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
+ if name_conf['default_jump_target'] == name_id:
+ raise ConfigError(f'Loop detected on default-jump-target.')
+ ## Now need to check that default-jump-target exists (other firewall chain/name)
+ if target not in dict_search_args(firewall['ipv4'], 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ verify_rule(firewall, rule_conf, False)
+
+ if 'ipv6' in firewall:
+ for name in ['name','forward','input','output']:
+ if name in firewall['ipv6']:
+ for name_id, name_conf in firewall['ipv6'][name].items():
+ if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
+ raise ConfigError('default-action set to jump, but no default-jump-target specified')
+ if 'default_jump_target' in name_conf:
+ target = name_conf['default_jump_target']
+ if 'jump' not in name_conf['default_action']:
+ raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
+ if name_conf['default_jump_target'] == name_id:
+ raise ConfigError(f'Loop detected on default-jump-target.')
+ ## Now need to check that default-jump-target exists (other firewall chain/name)
+ if target not in dict_search_args(firewall['ipv6'], 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ verify_rule(firewall, rule_conf, True)
return None
@@ -419,19 +329,6 @@ def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
- if 'zone' in firewall:
- for local_zone, local_zone_conf in firewall['zone'].items():
- if 'local_zone' not in local_zone_conf:
- continue
-
- local_zone_conf['from_local'] = {}
-
- for zone, zone_conf in firewall['zone'].items():
- if zone == local_zone or 'from' not in zone_conf:
- continue
- if local_zone in zone_conf['from']:
- local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
-
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
@@ -440,9 +337,8 @@ def apply_sysfs(firewall):
paths = glob(conf['sysfs'])
value = None
- if name in firewall:
- conf_value = firewall[name]
-
+ if name in firewall['global_options']:
+ conf_value = firewall['global_options'][name]
if conf_value in conf:
value = conf[conf_value]
elif conf_value == 'enable':
@@ -455,42 +351,6 @@ def apply_sysfs(firewall):
with open(path, 'w') as f:
f.write(value)
-def post_apply_trap(firewall):
- if 'first_install' in firewall:
- return None
-
- if 'config_trap' not in firewall or firewall['config_trap'] != 'enable':
- return None
-
- if not process_named_running('snmpd'):
- return None
-
- trap_username = os.getlogin()
-
- for host, target_conf in firewall['trap_targets'].items():
- community = target_conf['community'] if 'community' in target_conf else 'public'
- port = int(target_conf['port']) if 'port' in target_conf else 162
-
- base_cmd = f'snmptrap -v2c -c {community} {host}:{port} 0 {snmp_trap_mib}::{snmp_trap_name} '
-
- for change_type, changes in firewall['trap_diff'].items():
- for path_str, value in changes.items():
- objects = [
- f'mgmtEventUser s "{trap_username}"',
- f'mgmtEventSource i {snmp_event_source}',
- f'mgmtEventType i {snmp_change_type[change_type]}'
- ]
-
- if change_type == 'add':
- objects.append(f'mgmtEventCurrCfg s "{path_str} {value}"')
- elif change_type == 'delete':
- objects.append(f'mgmtEventPrevCfg s "{path_str} {value}"')
- elif change_type == 'change':
- objects.append(f'mgmtEventPrevCfg s "{path_str} {value[0]}"')
- objects.append(f'mgmtEventCurrCfg s "{path_str} {value[1]}"')
-
- cmd(base_cmd + ' '.join(objects))
-
def apply(firewall):
install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
@@ -515,8 +375,6 @@ def apply(firewall):
print('Updating GeoIP. Please wait...')
geoip_update(firewall)
- post_apply_trap(firewall)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 372bb0da7..71acd69fa 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -22,14 +22,13 @@ from ipaddress import ip_address
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.config import config_dict_merge
from vyos.configverify import verify_vrf
from vyos.ifconfig import Section
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -128,30 +127,19 @@ def get_config(config=None):
flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
+ # We have gathered the dict representation of the CLI, but there are
+ # default values which we need to conditionally update into the
+ # dictionary retrieved.
+ default_values = conf.get_config_defaults(**flow_accounting.kwargs,
+ recursive=True)
- # delete individual flow type default - should only be added if user uses
- # this feature
+ # delete individual flow type defaults - should only be added if user
+ # sets this feature
for flow_type in ['sflow', 'netflow']:
- if flow_type in default_values:
+ if flow_type not in flow_accounting and flow_type in default_values:
del default_values[flow_type]
- flow_accounting = dict_merge(default_values, flow_accounting)
- for flow_type in ['sflow', 'netflow']:
- if flow_type in flow_accounting:
- default_values = defaults(base + [flow_type])
- # we need to merge individual server configurations
- if 'server' in default_values:
- del default_values['server']
- flow_accounting[flow_type] = dict_merge(default_values, flow_accounting[flow_type])
-
- if 'server' in flow_accounting[flow_type]:
- default_values = defaults(base + [flow_type, 'server'])
- for server in flow_accounting[flow_type]['server']:
- flow_accounting[flow_type]['server'][server] = dict_merge(
- default_values,flow_accounting[flow_type]['server'][server])
+ flow_accounting = config_dict_merge(default_values, flow_accounting)
return flow_accounting
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index 7bdf448a3..793a90d88 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -24,12 +24,9 @@ from copy import deepcopy
import vyos.defaults
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
-from vyos.utils.process import cmd
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -72,8 +69,9 @@ def get_config(config=None):
return None
api_dict = conf.get_config_dict(base, key_mangling=('-', '_'),
- no_tag_node_value_mangle=True,
- get_first_key=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
# One needs to 'flatten' the keys dict from the config into the
# http-api.conf format for api_keys:
@@ -93,8 +91,8 @@ def get_config(config=None):
if 'api_keys' in api_dict:
keys_added = True
- if 'graphql' in api_dict:
- api_dict = dict_merge(defaults(base), api_dict)
+ if api_dict.from_defaults(['graphql']):
+ del api_dict['graphql']
http_api.update(api_dict)
diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py
index 4ec2f1835..40db417dd 100755
--- a/src/conf_mode/igmp_proxy.py
+++ b/src/conf_mode/igmp_proxy.py
@@ -21,11 +21,9 @@ 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
from vyos.utils.process import call
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -39,16 +37,9 @@ def get_config(config=None):
conf = Config()
base = ['protocols', 'igmp-proxy']
- igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- if 'interface' in igmp_proxy:
- # T2665: we must add the tagNode defaults individually until this is
- # moved to the base class
- default_values = defaults(base + ['interface'])
- for interface in igmp_proxy['interface']:
- igmp_proxy['interface'][interface] = dict_merge(default_values,
- igmp_proxy['interface'][interface])
-
+ igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_defaults=True)
if conf.exists(['protocols', 'igmp']):
igmp_proxy.update({'igmp_configured': ''})
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 1bdd61eca..c82f01e53 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -14,10 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os
-
from sys import exit
-from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
@@ -25,16 +22,13 @@ from vyos.configdict import node_changed
from vyos.configdict import is_member
from vyos.configdict import is_source_interface
from vyos.configdict import has_vlan_subinterface_configured
-from vyos.configdict import dict_merge
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vrf
from vyos.ifconfig import BridgeIf
from vyos.configdict import has_address_configured
from vyos.configdict import has_vrf_configured
-from vyos.xml import defaults
-from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
from vyos import ConfigError
@@ -61,22 +55,8 @@ def get_config(config=None):
else:
bridge.update({'member' : {'interface_remove' : tmp }})
- if dict_search('member.interface', bridge) != None:
- # XXX: T2665: we need a copy of the dict keys for iteration, else we will get:
- # RuntimeError: dictionary changed size during iteration
+ if dict_search('member.interface', bridge) is not None:
for interface in list(bridge['member']['interface']):
- for key in ['cost', 'priority']:
- if interface == key:
- del bridge['member']['interface'][key]
- continue
-
- # the default dictionary is not properly paged into the dict (see T2665)
- # thus we will ammend it ourself
- default_member_values = defaults(base + ['member', 'interface'])
- for interface,interface_config in bridge['member']['interface'].items():
- bridge['member']['interface'][interface] = dict_merge(
- default_member_values, bridge['member']['interface'][interface])
-
# Check if member interface is already member of another bridge
tmp = is_member(conf, interface, 'bridge')
if tmp and bridge['ifname'] not in tmp:
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 3f86e2638..0a927ac88 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 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
@@ -43,6 +43,14 @@ airbag.enable()
# XXX: wpa_supplicant works on the source interface
wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf'
+# Constants
+## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit
+GCM_AES_128_LEN: int = 32
+GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!'
+## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit
+GCM_AES_256_LEN: int = 64
+GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!'
+
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -89,18 +97,54 @@ def verify(macsec):
raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec))
if dict_search('security.encrypt', macsec) != None:
- if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None:
- raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!')
+ # Check that only static or MKA config is present
+ if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None):
+ raise ConfigError('Only static or MKA can be used!')
+
+ # Logic to check static configuration
+ if dict_search('security.static', macsec) != None:
+ # tx-key must be defined
+ if dict_search('security.static.key', macsec) == None:
+ raise ConfigError('Static MACsec tx-key must be defined.')
+
+ tx_len = len(dict_search('security.static.key', macsec))
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
+
+ # Make sure at least one peer is defined
+ if 'peer' not in macsec['security']['static']:
+ raise ConfigError('Must have at least one peer defined for static MACsec')
+
+ # For every enabled peer, make sure a MAC and rx-key is defined
+ for peer, peer_config in macsec['security']['static']['peer'].items():
+ if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config):
+ raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.')
+
+ # check rx-key length against cipher suite
+ rx_len = len(peer_config['key'])
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
+
+ # Logic to check MKA configuration
+ else:
+ if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None:
+ raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!')
- cak_len = len(dict_search('security.mka.cak', macsec))
+ cak_len = len(dict_search('security.mka.cak', macsec))
- if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32:
- # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit
- raise ConfigError('gcm-aes-128 requires a 128bit long key!')
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
- elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64:
- # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit
- raise ConfigError('gcm-aes-128 requires a 256bit long key!')
+ elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
if 'source_interface' in macsec:
# MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad
@@ -115,7 +159,9 @@ def verify(macsec):
def generate(macsec):
- render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec)
+ # Only generate wpa_supplicant config if using MKA
+ if dict_search('security.mka.cak', macsec):
+ render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec)
return None
@@ -142,8 +188,10 @@ def apply(macsec):
i = MACsecIf(**macsec)
i.update(macsec)
- if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
- call(f'systemctl reload-or-restart {systemd_service}')
+ # Only reload/restart if using MKA
+ if dict_search('security.mka.cak', macsec):
+ if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 2a9b43f9b..1d0feb56f 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -166,17 +166,23 @@ def verify_pki(openvpn):
raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}')
if tls:
- if 'ca_certificate' not in tls:
- raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
+ if (mode in ['server', 'client']) and ('ca_certificate' not in tls):
+ raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\
+ it is required in server and client modes')
+ else:
+ if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls):
+ raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\
+ on openvpn interface {interface} in site-to-site mode')
- for ca_name in tls['ca_certificate']:
- if ca_name not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+ if 'ca_certificate' in tls:
+ for ca_name in tls['ca_certificate']:
+ if ca_name not in pki['ca']:
+ raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
- if len(tls['ca_certificate']) > 1:
- sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
- if not verify_ca_chain(sorted_chain, pki['ca']):
- raise ConfigError(f'CA certificates are not a valid chain')
+ if len(tls['ca_certificate']) > 1:
+ sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
+ if not verify_ca_chain(sorted_chain, pki['ca']):
+ raise ConfigError(f'CA certificates are not a valid chain')
if mode != 'client' and 'auth_key' not in tls:
if 'certificate' not in tls:
@@ -189,16 +195,7 @@ def verify_pki(openvpn):
if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None:
raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}')
- if mode == 'server' and 'dh_params' not in tls and not is_ec_private_key(pki, tls['certificate']):
- raise ConfigError('Must specify "tls dh-params" when not using EC keys in server mode')
-
if 'dh_params' in tls:
- if 'dh' not in pki:
- raise ConfigError('There are no DH parameters in PKI configuration')
-
- if tls['dh_params'] not in pki['dh']:
- raise ConfigError(f'Invalid dh-params on openvpn interface {interface}')
-
pki_dh = pki['dh'][tls['dh_params']]
dh_params = load_dh_parameters(pki_dh['parameters'])
dh_numbers = dh_params.parameter_numbers()
@@ -207,6 +204,7 @@ def verify_pki(openvpn):
if dh_bits < 2048:
raise ConfigError(f'Minimum DH key-size is 2048 bits')
+
if 'auth_key' in tls or 'crypt_key' in tls:
if not dict_search_args(pki, 'openvpn', 'shared_secret'):
raise ConfigError('There are no openvpn shared-secrets in PKI configuration')
@@ -495,9 +493,6 @@ def verify(openvpn):
if openvpn['protocol'] == 'tcp-active':
raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"')
- if not dict_search('tls.dh_params', openvpn):
- raise ConfigError('Must specify "tls dh-params" when "tls role" is "passive"')
-
if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']):
if 'dh_params' in openvpn['tls']:
print('Warning: using dh-params and EC keys simultaneously will ' \
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 6a075970e..91aed9cc3 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -55,6 +55,9 @@ def get_config(config=None):
tmp = is_node_changed(conf, base + [ifname, 'encapsulation'])
if tmp: tunnel.update({'encapsulation_changed': {}})
+ tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key'])
+ if tmp: tunnel.update({'key_changed': {}})
+
# We also need to inspect other configured tunnels as there are Kernel
# restrictions where we need to comply. E.g. GRE tunnel key can't be used
# twice, or with multiple GRE tunnels to the same location we must specify
@@ -197,7 +200,8 @@ def apply(tunnel):
remote = dict_search('linkinfo.info_data.remote', tmp)
if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in
- ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any']):
+ ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or
+ 'key_changed' in tunnel):
if interface in interfaces():
tmp = Interface(interface)
tmp.remove()
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 446399255..122d9589a 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -90,7 +90,6 @@ def verify(wireguard):
# run checks on individual configured WireGuard peer
public_keys = []
-
for tmp in wireguard['peer']:
peer = wireguard['peer'][tmp]
@@ -107,8 +106,9 @@ def verify(wireguard):
if peer['public_key'] in public_keys:
raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
- if 'disable' not in peer and is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
- raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
+ if 'disable' not in peer:
+ if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
+ raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
public_keys.append(peer['public_key'])
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 42326bea0..02b4a2500 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -25,8 +25,6 @@ from vyos.configdict import get_interface_dict
from vyos.configdict import dict_merge
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
-from vyos.configverify import verify_dhcpv6
-from vyos.configverify import verify_source_interface
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
@@ -42,6 +40,8 @@ airbag.enable()
# XXX: wpa_supplicant works on the source interface
wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf'
hostapd_conf = '/run/hostapd/{ifname}.conf'
+hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf'
+hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf'
def find_other_stations(conf, base, ifname):
"""
@@ -79,30 +79,14 @@ def get_config(config=None):
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(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:
+ if 'deleted' not in wifi:
+ # then get_interface_dict provides default keys
+ if wifi.from_defaults(['security', 'wep']): # if not set by user
+ del wifi['security']['wep']
+ if wifi.from_defaults(['security', 'wpa']): # if not set by user
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
- if dict_search('security.wpa.radius.server.port', wifi) != None:
- del wifi['security']['wpa']['radius']['server']['port']
- if not len(wifi['security']['wpa']['radius']['server']):
- del wifi['security']['wpa']['radius']
- if not len(wifi['security']['wpa']):
- del wifi['security']['wpa']
- if not len(wifi['security']):
- del wifi['security']
- if 'security' in wifi and 'wpa' in wifi['security']:
+ if dict_search('security.wpa', wifi) != None:
wpa_cipher = wifi['security']['wpa'].get('cipher')
wpa_mode = wifi['security']['wpa'].get('mode')
if not wpa_cipher:
@@ -120,13 +104,9 @@ def get_config(config=None):
tmp = find_other_stations(conf, base, wifi['ifname'])
if tmp: wifi['station_interfaces'] = tmp
- # Add individual RADIUS server default values
- if dict_search('security.wpa.radius.server', wifi):
- default_values = defaults(base + ['security', 'wpa', 'radius', 'server'])
-
- for server in dict_search('security.wpa.radius.server', wifi):
- wifi['security']['wpa']['radius']['server'][server] = dict_merge(
- default_values, wifi['security']['wpa']['radius']['server'][server])
+ # used in hostapt.conf.j2
+ wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi)
+ wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi)
return wifi
@@ -142,7 +122,7 @@ def verify(wifi):
raise ConfigError('You must specify a WiFi mode')
if 'ssid' not in wifi and wifi['type'] != 'monitor':
- raise ConfigError('SSID must be configured')
+ raise ConfigError('SSID must be configured unless type is set to "monitor"!')
if wifi['type'] == 'access-point':
if 'country_code' not in wifi:
@@ -215,7 +195,10 @@ def generate(wifi):
if 'deleted' in wifi:
if os.path.isfile(hostapd_conf.format(**wifi)):
os.unlink(hostapd_conf.format(**wifi))
-
+ if os.path.isfile(hostapd_accept_station_conf.format(**wifi)):
+ os.unlink(hostapd_accept_station_conf.format(**wifi))
+ if os.path.isfile(hostapd_deny_station_conf.format(**wifi)):
+ os.unlink(hostapd_deny_station_conf.format(**wifi))
if os.path.isfile(wpa_suppl_conf.format(**wifi)):
os.unlink(wpa_suppl_conf.format(**wifi))
@@ -250,12 +233,12 @@ def generate(wifi):
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
- render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2',
- wifi)
+ render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi)
+ render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi)
+ render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi)
elif wifi['type'] == 'station':
- render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2',
- wifi)
+ render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi)
return None
diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py
index c8f341327..c2e87d171 100755
--- a/src/conf_mode/lldp.py
+++ b/src/conf_mode/lldp.py
@@ -20,13 +20,11 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import is_loopback_addr
from vyos.version import get_version_data
from vyos.utils.process import call
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos.template import render
from vyos import ConfigError
from vyos import airbag
@@ -46,7 +44,9 @@ def get_config(config=None):
return {}
lldp = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
if conf.exists(['service', 'snmp']):
lldp['system_snmp_enabled'] = ''
@@ -54,27 +54,12 @@ def get_config(config=None):
version_data = get_version_data()
lldp['version'] = version_data['version']
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- # location coordinates have a default value
- if 'interface' in lldp:
- for interface, interface_config in lldp['interface'].items():
- default_values = defaults(base + ['interface'])
- if dict_search('location.coordinate_based', interface_config) == None:
- # no location specified - no need to add defaults
- del default_values['location']['coordinate_based']['datum']
- del default_values['location']['coordinate_based']['altitude']
-
- # cleanup default_values dictionary from inner to outer
- # this might feel overkill here, but it does support easy extension
- # in the future with additional default values
- if len(default_values['location']['coordinate_based']) == 0:
- del default_values['location']['coordinate_based']
- if len(default_values['location']) == 0:
- del default_values['location']
-
- lldp['interface'][interface] = dict_merge(default_values,
- lldp['interface'][interface])
+ # prune location information if not set by user
+ for interface in lldp.get('interface', []):
+ if lldp.from_defaults(['interface', interface, 'location']):
+ del lldp['interface'][interface]['location']
+ elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']):
+ del lldp['interface'][interface]['location']['coordinate_based']
return lldp
diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py
index 2fb0edf8e..8fe429653 100755
--- a/src/conf_mode/load-balancing-haproxy.py
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -20,14 +20,12 @@ from sys import exit
from shutil import rmtree
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.utils.process import call
from vyos.utils.network import check_port_availability
from vyos.utils.network import is_listen_port_bind_service
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -54,18 +52,8 @@ def get_config(config=None):
lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- # 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)
- if 'backend' in default_values:
- del default_values['backend']
if lb:
- lb = dict_merge(default_values, lb)
-
- if 'backend' in lb:
- for backend in lb['backend']:
- default_balues_backend = defaults(base + ['backend'])
- lb['backend'][backend] = dict_merge(default_balues_backend, lb['backend'][backend])
+ lb = conf.merge_defaults(lb, recursive=True)
return lb
diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py
index 3533a5a04..ad9c80d72 100755
--- a/src/conf_mode/load-balancing-wan.py
+++ b/src/conf_mode/load-balancing-wan.py
@@ -21,10 +21,8 @@ from shutil import rmtree
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.utils.process import cmd
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -41,48 +39,15 @@ def get_config(config=None):
conf = Config()
base = ['load-balancing', 'wan']
- lb = conf.get_config_dict(base,
+ lb = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
get_first_key=True,
- key_mangling=('-', '_'),
- no_tag_node_value_mangle=True)
-
- # 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)
- # lb base default values can not be merged here - remove and add them later
- if 'interface_health' in default_values:
- del default_values['interface_health']
- if 'rule' in default_values:
- del default_values['rule']
- lb = dict_merge(default_values, lb)
-
- if 'interface_health' in lb:
- for iface in lb.get('interface_health'):
- default_values_iface = defaults(base + ['interface-health'])
- if 'test' in default_values_iface:
- del default_values_iface['test']
- lb['interface_health'][iface] = dict_merge(
- default_values_iface, lb['interface_health'][iface])
- if 'test' in lb['interface_health'][iface]:
- for node_test in lb['interface_health'][iface]['test']:
- default_values_test = defaults(base +
- ['interface-health', 'test'])
- lb['interface_health'][iface]['test'][node_test] = dict_merge(
- default_values_test,
- lb['interface_health'][iface]['test'][node_test])
-
- if 'rule' in lb:
- for rule in lb.get('rule'):
- default_values_rule = defaults(base + ['rule'])
- if 'interface' in default_values_rule:
- del default_values_rule['interface']
- lb['rule'][rule] = dict_merge(default_values_rule, lb['rule'][rule])
- if not conf.exists(base + ['rule', rule, 'limit']):
- del lb['rule'][rule]['limit']
- if 'interface' in lb['rule'][rule]:
- for iface in lb['rule'][rule]['interface']:
- default_values_rule_iface = defaults(base + ['rule', 'interface'])
- lb['rule'][rule]['interface'][iface] = dict_merge(default_values_rule_iface, lb['rule'][rule]['interface'][iface])
+ with_recursive_defaults=True)
+
+ # prune limit key if not set by user
+ for rule in lb.get('rule', []):
+ if lb.from_defaults(['rule', rule, 'limit']):
+ del lb['rule'][rule]['limit']
return lb
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 8e3a11ff4..9da7fbe80 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -25,7 +25,6 @@ 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
from vyos.template import is_ip_network
from vyos.utils.kernel import check_kmod
@@ -34,7 +33,6 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -145,16 +143,9 @@ def get_config(config=None):
conf = Config()
base = ['nat']
- nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # T2665: we must add the tagNode defaults individually until this is
- # moved to the base class
- for direction in ['source', 'destination', 'static']:
- if direction in nat:
- default_values = defaults(base + [direction, 'rule'])
- for rule in dict_search(f'{direction}.rule', nat) or []:
- nat[direction]['rule'][rule] = dict_merge(default_values,
- nat[direction]['rule'][rule])
+ nat = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
# read in current nftable (once) for further processing
tmp = cmd('nft -j list table raw')
@@ -233,7 +224,7 @@ def verify(nat):
elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
- if not dict_search('translation.address', config) and not dict_search('translation.port', config) and not dict_search('translation.redirect.port', config):
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:
if 'exclude' not in config and 'backend' not in config['load_balance']:
raise ConfigError(f'{err_msg} translation requires address and/or port')
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 25f625b84..4c12618bc 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -23,13 +23,11 @@ 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
from vyos.utils.process import cmd
from vyos.utils.kernel import check_kmod
from vyos.utils.dict import dict_search
from vyos.template import is_ipv6
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -60,16 +58,6 @@ def get_config(config=None):
base = ['nat66']
nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # T2665: we must add the tagNode defaults individually until this is
- # moved to the base class
- for direction in ['source', 'destination']:
- if direction in nat:
- default_values = defaults(base + [direction, 'rule'])
- if 'rule' in nat[direction]:
- for rule in nat[direction]['rule']:
- nat[direction]['rule'][rule] = dict_merge(default_values,
- nat[direction]['rule'][rule])
-
# read in current nftable (once) for further processing
tmp = cmd('nft -j list table ip6 raw')
nftable_json = json.loads(tmp)
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index eb8cb3940..34ba2fe69 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -18,7 +18,6 @@ from sys import exit
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
-from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.pki import is_ca_certificate
from vyos.pki import load_certificate
@@ -28,7 +27,6 @@ from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
from vyos.utils.dict import dict_search_args
from vyos.utils.dict import dict_search_recursive
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -113,8 +111,7 @@ def get_config(config=None):
# We only merge on the defaults of there is a configuration at all
if conf.exists(base):
- default_values = defaults(base)
- pki = dict_merge(default_values, pki)
+ pki = conf.merge_defaults(pki, recursive=True)
# We need to get the entire system configuration to verify that we are not
# deleting a certificate that is still referenced somewhere!
diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py
index f5ac56f65..104711b55 100755
--- a/src/conf_mode/protocols_babel.py
+++ b/src/conf_mode/protocols_babel.py
@@ -19,13 +19,13 @@ import os
from sys import exit
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos.template import render_to_string
from vyos import ConfigError
from vyos import frr
@@ -38,7 +38,8 @@ def get_config(config=None):
else:
conf = Config()
base = ['protocols', 'babel']
- babel = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ babel = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# FRR has VRF support for different routing daemons. As interfaces belong
# to VRFs - or the global VRF, we need to check for changed interfaces so
@@ -54,15 +55,13 @@ def get_config(config=None):
return babel
# 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)
+ # values which we need to update into the dictionary retrieved.
+ default_values = conf.get_config_defaults(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ recursive=True)
- # XXX: T2665: we currently have no nice way for defaults under tag nodes,
- # clean them out and add them manually :(
- del default_values['interface']
-
- # merge in remaining default values
- babel = dict_merge(default_values, babel)
+ # merge in default values
+ babel = config_dict_merge(default_values, babel)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index b8d2d65ee..dab784662 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -17,12 +17,10 @@
import os
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
from vyos.template import is_ipv6
from vyos.template import render_to_string
from vyos.utils.network import is_ipv6_link_local
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -41,18 +39,7 @@ def get_config(config=None):
if not conf.exists(base):
return bfd
- # We have gathered the dict representation of the CLI, but there are
- # default options which we need to update into the dictionary retrived.
- # XXX: T2665: we currently have no nice way for defaults under tag
- # nodes, thus we load the defaults "by hand"
- default_values = defaults(base + ['peer'])
- if 'peer' in bfd:
- for peer in bfd['peer']:
- bfd['peer'][peer] = dict_merge(default_values, bfd['peer'][peer])
-
- if 'profile' in bfd:
- for profile in bfd['profile']:
- bfd['profile'][profile] = dict_merge(default_values, bfd['profile'][profile])
+ bfd = conf.merge_defaults(bfd, recursive=True)
return bfd
diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py
index faf56d741..e7e44db84 100755
--- a/src/conf_mode/protocols_failover.py
+++ b/src/conf_mode/protocols_failover.py
@@ -19,10 +19,8 @@ import json
from pathlib import Path
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -42,15 +40,12 @@ def get_config(config=None):
conf = Config()
base = ['protocols', 'failover']
- failover = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ failover = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
# Set default values only if we set config
- if failover.get('route'):
- for route, route_config in failover.get('route').items():
- for next_hop, next_hop_config in route_config.get('next_hop').items():
- default_values = defaults(base + ['route'])
- failover['route'][route]['next_hop'][next_hop] = dict_merge(
- default_values['next_hop'], failover['route'][route]['next_hop'][next_hop])
+ if failover.get('route') is not None:
+ failover = conf.merge_defaults(failover, recursive=True)
return failover
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 4c637a99f..e00c58ee4 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -28,7 +28,6 @@ from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.template import render_to_string
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -64,19 +63,14 @@ def get_config(config=None):
if interfaces_removed:
isis['interface_removed'] = list(interfaces_removed)
- # Bail out early if configuration tree does not exist
+ # Bail out early if configuration tree does no longer exist. this must
+ # be done after retrieving the list of interfaces to be removed.
if not conf.exists(base):
isis.update({'deleted' : ''})
return isis
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- # XXX: Note that we can not call defaults(base), as defaults does not work
- # on an instance of a tag node. As we use the exact same CLI definition for
- # both the non-vrf and vrf version this is absolutely safe!
- default_values = defaults(base_path)
# merge in default values
- isis = dict_merge(default_values, isis)
+ isis = conf.merge_defaults(isis, recursive=True)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
@@ -254,7 +248,7 @@ def apply(isis):
if key not in isis:
continue
for interface in isis[key]:
- frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
if 'frr_isisd_config' in isis:
frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config'])
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index f2075d25b..cddd3765e 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 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
@@ -20,6 +20,7 @@ from sys import exit
from sys import argv
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_common_route_maps
@@ -29,7 +30,6 @@ from vyos.configverify import verify_access_list
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -65,17 +65,15 @@ def get_config(config=None):
if interfaces_removed:
ospf['interface_removed'] = list(interfaces_removed)
- # Bail out early if configuration tree does not exist
+ # Bail out early if configuration tree does no longer exist. this must
+ # be done after retrieving the list of interfaces to be removed.
if not conf.exists(base):
ospf.update({'deleted' : ''})
return ospf
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
- # XXX: Note that we can not call defaults(base), as defaults does not work
- # on an instance of a tag node. As we use the exact same CLI definition for
- # both the non-vrf and vrf version this is absolutely safe!
- default_values = defaults(base_path)
+ default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True)
# We have to cleanup the default dict, as default values could enable features
# which are not explicitly enabled on the CLI. Example: default-information
@@ -84,62 +82,27 @@ def get_config(config=None):
# need to check this first and probably drop that key.
if dict_search('default_information.originate', ospf) is None:
del default_values['default_information']
- if dict_search('area.area_type.nssa', ospf) is None:
- del default_values['area']['area_type']['nssa']
if 'mpls_te' not in ospf:
del default_values['mpls_te']
if 'graceful_restart' not in ospf:
del default_values['graceful_restart']
+ for area_num in default_values.get('area', []):
+ if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
+ del default_values['area'][area_num]['area_type']['nssa']
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']:
- # table is a tagNode thus we need to clean out all occurances for the
- # default values and load them in later individually
- if protocol == 'table':
- del default_values['redistribute']['table']
- continue
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
if dict_search(f'redistribute.{protocol}', ospf) is None:
del default_values['redistribute'][protocol]
- # XXX: T2665: we currently have no nice way for defaults under tag nodes,
- # clean them out and add them manually :(
- del default_values['neighbor']
- del default_values['area']['virtual_link']
- del default_values['interface']
-
- # merge in remaining default values
- ospf = dict_merge(default_values, ospf)
-
- if 'neighbor' in ospf:
- default_values = defaults(base + ['neighbor'])
- for neighbor in ospf['neighbor']:
- ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor])
+ for interface in ospf.get('interface', []):
+ # We need to reload the defaults on every pass b/c of
+ # hello-multiplier dependency on dead-interval
+ # If hello-multiplier is set, we need to remove the default from
+ # dead-interval.
+ if 'hello_multiplier' in ospf['interface'][interface]:
+ del default_values['interface'][interface]['dead_interval']
- if 'area' in ospf:
- default_values = defaults(base + ['area', 'virtual-link'])
- for area, area_config in ospf['area'].items():
- if 'virtual_link' in area_config:
- for virtual_link in area_config['virtual_link']:
- ospf['area'][area]['virtual_link'][virtual_link] = dict_merge(
- default_values, ospf['area'][area]['virtual_link'][virtual_link])
-
- if 'interface' in ospf:
- for interface in ospf['interface']:
- # We need to reload the defaults on every pass b/c of
- # hello-multiplier dependency on dead-interval
- default_values = defaults(base + ['interface'])
- # If hello-multiplier is set, we need to remove the default from
- # dead-interval.
- if 'hello_multiplier' in ospf['interface'][interface]:
- del default_values['dead_interval']
-
- ospf['interface'][interface] = dict_merge(default_values,
- ospf['interface'][interface])
-
- if 'redistribute' in ospf and 'table' in ospf['redistribute']:
- default_values = defaults(base + ['redistribute', 'table'])
- for table in ospf['redistribute']['table']:
- ospf['redistribute']['table'][table] = dict_merge(default_values,
- ospf['redistribute']['table'][table])
+ ospf = config_dict_merge(default_values, ospf)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
@@ -287,7 +250,7 @@ def apply(ospf):
if key not in ospf:
continue
for interface in ospf[key]:
- frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
if 'frr_ospfd_config' in ospf:
frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config'])
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index fbea51f56..5b1adce30 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -20,6 +20,7 @@ from sys import exit
from sys import argv
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_common_route_maps
@@ -29,7 +30,6 @@ from vyos.template import render_to_string
from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -64,17 +64,16 @@ def get_config(config=None):
if interfaces_removed:
ospfv3['interface_removed'] = list(interfaces_removed)
- # Bail out early if configuration tree does not exist
+ # Bail out early if configuration tree does no longer exist. this must
+ # be done after retrieving the list of interfaces to be removed.
if not conf.exists(base):
ospfv3.update({'deleted' : ''})
return ospfv3
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
- # XXX: Note that we can not call defaults(base), as defaults does not work
- # on an instance of a tag node. As we use the exact same CLI definition for
- # both the non-vrf and vrf version this is absolutely safe!
- default_values = defaults(base_path)
+ default_values = conf.get_config_defaults(**ospfv3.kwargs,
+ recursive=True)
# We have to cleanup the default dict, as default values could enable features
# which are not explicitly enabled on the CLI. Example: default-information
@@ -86,12 +85,10 @@ def get_config(config=None):
if 'graceful_restart' not in ospfv3:
del default_values['graceful_restart']
- # XXX: T2665: we currently have no nice way for defaults under tag nodes,
- # clean them out and add them manually :(
- del default_values['interface']
+ default_values.pop('interface', {})
# merge in remaining default values
- ospfv3 = dict_merge(default_values, ospfv3)
+ ospfv3 = config_dict_merge(default_values, ospfv3)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
@@ -170,7 +167,7 @@ def apply(ospfv3):
if key not in ospfv3:
continue
for interface in ospfv3[key]:
- frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True)
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
if 'new_frr_config' in ospfv3:
frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config'])
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 5661dc377..bd47dfd00 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -25,7 +25,6 @@ from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos.template import render_to_string
from vyos import ConfigError
from vyos import frr
@@ -55,9 +54,7 @@ def get_config(config=None):
# 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)
- # merge in remaining default values
- rip = dict_merge(default_values, rip)
+ rip = conf.merge_defaults(rip, recursive=True)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
index e3c904e33..dd1550033 100755
--- a/src/conf_mode/protocols_ripng.py
+++ b/src/conf_mode/protocols_ripng.py
@@ -24,7 +24,6 @@ from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos.template import render_to_string
from vyos import ConfigError
from vyos import frr
@@ -45,9 +44,7 @@ def get_config(config=None):
# 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)
- # merge in remaining default values
- ripng = dict_merge(default_values, ripng)
+ ripng = conf.merge_defaults(ripng, recursive=True)
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index 035b7db05..05e876f3b 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -19,10 +19,8 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -43,8 +41,7 @@ def get_config(config=None):
# 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)
- rpki = dict_merge(default_values, rpki)
+ rpki = conf.merge_defaults(rpki, recursive=True)
return rpki
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index 53e9ff50d..5536adda2 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -38,7 +38,6 @@ from vyos.qos import TrafficShaper
from vyos.qos import TrafficShaperHFSC
from vyos.utils.process import call
from vyos.utils.dict import dict_search_recursive
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -97,63 +96,32 @@ def get_config(config=None):
type_node = path.split(" ")[1] # return only interface type node
set_dependents(type_node, conf, ifname)
- if 'policy' in qos:
- for policy in qos['policy']:
- # when calling defaults() we need to use the real CLI node, thus we
- # need a hyphen
- policy_hyphen = policy.replace('_', '-')
-
- if policy in ['random_detect']:
- for rd_name, rd_config in qos['policy'][policy].items():
- # There are eight precedence levels - ensure all are present
- # to be filled later down with the appropriate default values
- default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {},
- '4' : {}, '5' : {}, '6' : {}, '7' : {} }}
- qos['policy']['random_detect'][rd_name] = dict_merge(
- default_precedence, qos['policy']['random_detect'][rd_name])
-
- for p_name, p_config in qos['policy'][policy].items():
- default_values = defaults(base + ['policy', policy_hyphen])
-
- if policy in ['priority_queue']:
- if 'default' not in p_config:
- raise ConfigError(f'QoS policy {p_name} misses "default" class!')
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if 'class' in default_values:
- del default_values['class']
- if 'precedence' in default_values:
- del default_values['precedence']
-
- qos['policy'][policy][p_name] = dict_merge(
- default_values, qos['policy'][policy][p_name])
-
- # class is another tag node which requires individual handling
- if 'class' in p_config:
- default_values = defaults(base + ['policy', policy_hyphen, 'class'])
- for p_class in p_config['class']:
- qos['policy'][policy][p_name]['class'][p_class] = dict_merge(
- default_values, qos['policy'][policy][p_name]['class'][p_class])
-
- if 'precedence' in p_config:
- default_values = defaults(base + ['policy', policy_hyphen, 'precedence'])
- # precedence values are a bit more complex as they are calculated
- # under specific circumstances - thus we need to iterate two times.
- # first blend in the defaults from XML / CLI
- for precedence in p_config['precedence']:
- qos['policy'][policy][p_name]['precedence'][precedence] = dict_merge(
- default_values, qos['policy'][policy][p_name]['precedence'][precedence])
- # second calculate defaults based on actual dictionary
- for precedence in p_config['precedence']:
- max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold'])
- if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]:
- qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str(
- int((9 + int(precedence)) * max_thr) // 18);
-
- if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]:
- qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \
- str(int(4 * max_thr))
+ for policy in qos.get('policy', []):
+ if policy in ['random_detect']:
+ for rd_name in list(qos['policy'][policy]):
+ # There are eight precedence levels - ensure all are present
+ # to be filled later down with the appropriate default values
+ default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {},
+ '4' : {}, '5' : {}, '6' : {}, '7' : {} }}
+ qos['policy']['random_detect'][rd_name] = dict_merge(
+ default_precedence, qos['policy']['random_detect'][rd_name])
+
+ qos = conf.merge_defaults(qos, recursive=True)
+
+ for policy in qos.get('policy', []):
+ for p_name, p_config in qos['policy'][policy].items():
+ if 'precedence' in p_config:
+ # precedence settings are a bit more complex as they are
+ # calculated under specific circumstances:
+ for precedence in p_config['precedence']:
+ max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold'])
+ if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]:
+ qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str(
+ int((9 + int(precedence)) * max_thr) // 18);
+
+ if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]:
+ qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \
+ str(int(4 * max_thr))
return qos
@@ -202,7 +170,9 @@ def verify(qos):
queue_lim = int(precedence_config['queue_limit'])
if queue_lim < max_tr:
raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!')
-
+ if policy_type in ['priority_queue']:
+ if 'default' not in policy_config:
+ raise ConfigError(f'Policy {policy} misses "default" class!')
if 'default' in policy_config:
if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin']:
raise ConfigError('Bandwidth not defined for default traffic!')
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index 3ff7880b2..a8fce8e01 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -22,12 +22,10 @@ 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.utils.process import call
from vyos.utils.permission import chown
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -55,8 +53,7 @@ def get_config(config=None):
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)
+ salt = conf.merge_defaults(salt, recursive=True)
if not conf.exists(base):
return None
diff --git a/src/conf_mode/service_config_sync.py b/src/conf_mode/service_config_sync.py
index 5cde735a1..4b8a7f6ee 100755
--- a/src/conf_mode/service_config_sync.py
+++ b/src/conf_mode/service_config_sync.py
@@ -19,8 +19,6 @@ import json
from pathlib import Path
from vyos.config import Config
-from vyos.configdict import dict_merge
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -42,12 +40,8 @@ def get_config(config=None):
base = ['service', 'config-sync']
if not conf.exists(base):
return None
- config = conf.get_config_dict(base,
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- default_values = defaults(base)
- config = dict_merge(default_values, config)
+ config = conf.get_config_dict(base, get_first_key=True,
+ with_recursive_defaults=True)
return config
diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py
index 7eb41ea87..b112add3f 100755
--- a/src/conf_mode/service_console-server.py
+++ b/src/conf_mode/service_console-server.py
@@ -20,10 +20,8 @@ from sys import exit
from psutil import process_iter
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
config_file = '/run/conserver/conserver.cf'
@@ -49,11 +47,7 @@ def get_config(config=None):
# 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 + ['device'])
- if 'device' in proxy:
- for device in proxy['device']:
- tmp = dict_merge(default_values, proxy['device'][device])
- proxy['device'][device] = tmp
+ proxy = conf.merge_defaults(proxy, recursive=True)
return proxy
diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py
index f6b80552b..276a71fcb 100755
--- a/src/conf_mode/service_ids_fastnetmon.py
+++ b/src/conf_mode/service_ids_fastnetmon.py
@@ -19,10 +19,8 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -41,11 +39,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- fastnetmon = dict_merge(default_values, fastnetmon)
+ fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return fastnetmon
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 0269bedd9..40eb13e23 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -22,7 +22,6 @@ from sys import exit
from shutil import rmtree
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.ifconfig import Section
@@ -30,7 +29,6 @@ from vyos.template import render
from vyos.utils.process import call
from vyos.utils.permission import chown
from vyos.utils.process import cmd
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -83,8 +81,7 @@ def get_config(config=None):
# 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)
- monitoring = dict_merge(default_values, monitoring)
+ monitoring = conf.merge_defaults(monitoring, recursive=True)
monitoring['custom_scripts_dir'] = custom_scripts_dir
monitoring['hostname'] = get_hostname()
diff --git a/src/conf_mode/service_monitoring_zabbix-agent.py b/src/conf_mode/service_monitoring_zabbix-agent.py
new file mode 100755
index 000000000..98d8a32ca
--- /dev/null
+++ b/src/conf_mode/service_monitoring_zabbix-agent.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.utils.process import call
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+service_name = 'zabbix-agent2'
+service_conf = f'/run/zabbix/{service_name}.conf'
+systemd_override = r'/run/systemd/system/zabbix-agent2.service.d/10-override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['service', 'monitoring', 'zabbix-agent']
+
+ if not conf.exists(base):
+ return None
+
+ config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+
+ # Cut the / from the end, /tmp/ => /tmp
+ if 'directory' in config and config['directory'].endswith('/'):
+ config['directory'] = config['directory'][:-1]
+
+ return config
+
+
+def verify(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ return
+
+ if 'server' not in config:
+ raise ConfigError('Server is required!')
+
+
+def generate(config):
+ # bail out early - looks like removal from running config
+ if config is None:
+ # Remove old config and return
+ config_files = [service_conf, systemd_override]
+ for file in config_files:
+ if os.path.isfile(file):
+ os.unlink(file)
+
+ return None
+
+ # Write configuration file
+ render(service_conf, 'zabbix-agent/zabbix-agent.conf.j2', config)
+ render(systemd_override, 'zabbix-agent/10-override.conf.j2', config)
+
+ return None
+
+
+def apply(config):
+ call('systemctl daemon-reload')
+ if config:
+ call(f'systemctl restart {service_name}.service')
+ else:
+ call(f'systemctl stop {service_name}.service')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index fe33c43ea..dbb47de4e 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -19,10 +19,8 @@ import os
from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -35,40 +33,9 @@ def get_config(config=None):
else:
conf = Config()
base = ['service', 'router-advert']
- rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_interface_values = defaults(base + ['interface'])
- # we deal with prefix, route defaults later on
- if 'prefix' in default_interface_values:
- del default_interface_values['prefix']
- if 'route' in default_interface_values:
- del default_interface_values['route']
-
- default_prefix_values = defaults(base + ['interface', 'prefix'])
- default_route_values = defaults(base + ['interface', 'route'])
-
- if 'interface' in rtradv:
- for interface in rtradv['interface']:
- rtradv['interface'][interface] = dict_merge(
- default_interface_values, rtradv['interface'][interface])
-
- if 'prefix' in rtradv['interface'][interface]:
- for prefix in rtradv['interface'][interface]['prefix']:
- rtradv['interface'][interface]['prefix'][prefix] = dict_merge(
- default_prefix_values, rtradv['interface'][interface]['prefix'][prefix])
-
- if 'route' in rtradv['interface'][interface]:
- for route in rtradv['interface'][interface]['route']:
- rtradv['interface'][interface]['route'][route] = dict_merge(
- default_route_values, rtradv['interface'][interface]['route'][route])
-
- if 'name_server' in rtradv['interface'][interface]:
- # always use a list when dealing with nameservers - eases the template generation
- if isinstance(rtradv['interface'][interface]['name_server'], str):
- rtradv['interface'][interface]['name_server'] = [
- rtradv['interface'][interface]['name_server']]
+ rtradv = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return rtradv
diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py
index 54b72e029..ba5e645f0 100755
--- a/src/conf_mode/service_sla.py
+++ b/src/conf_mode/service_sla.py
@@ -19,10 +19,8 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -44,11 +42,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- sla = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- sla = dict_merge(default_values, sla)
+ sla = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
# Ignore default XML values if config doesn't exists
# Delete key from dict
diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py
index b37d502c2..cf26bf9ce 100755
--- a/src/conf_mode/service_upnp.py
+++ b/src/conf_mode/service_upnp.py
@@ -23,12 +23,10 @@ from ipaddress import IPv4Network
from ipaddress import IPv6Network
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.utils.process import call
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -47,10 +45,7 @@ def get_config(config=None):
if not upnpd:
return None
- if 'rule' in upnpd:
- default_member_values = defaults(base + ['rule'])
- for rule,rule_config in upnpd['rule'].items():
- upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule])
+ upnpd = conf.merge_defaults(upnpd, recursive=True)
uuidgen = uuid.uuid1()
upnpd.update({'uuid': uuidgen})
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
index db4066572..12ae4135e 100755
--- a/src/conf_mode/service_webproxy.py
+++ b/src/conf_mode/service_webproxy.py
@@ -20,14 +20,13 @@ from shutil import rmtree
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
+from vyos.config import config_dict_merge
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.permission import chmod_755
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos.base import Warning
from vyos import ConfigError
from vyos import airbag
@@ -125,7 +124,8 @@ def get_config(config=None):
get_first_key=True)
# 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)
+ default_values = conf.get_config_defaults(**proxy.kwargs,
+ recursive=True)
# if no authentication method is supplied, no need to add defaults
if not dict_search('authentication.method', proxy):
@@ -138,16 +138,7 @@ def get_config(config=None):
proxy['squidguard_conf'] = squidguard_config_file
proxy['squidguard_db_dir'] = squidguard_db_dir
- # XXX: T2665: blend in proper cache-peer default values later
- default_values.pop('cache_peer')
- proxy = dict_merge(default_values, proxy)
-
- # XXX: T2665: blend in proper cache-peer default values
- if 'cache_peer' in proxy:
- default_values = defaults(base + ['cache-peer'])
- for peer in proxy['cache_peer']:
- proxy['cache_peer'][peer] = dict_merge(default_values,
- proxy['cache_peer'][peer])
+ proxy = config_dict_merge(default_values, proxy)
return proxy
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 4bf67f079..7882f8510 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -31,7 +31,6 @@ from vyos.utils.permission import chmod_755
from vyos.utils.dict import dict_search
from vyos.utils.network import is_addr_assigned
from vyos.version import get_version_data
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -70,26 +69,9 @@ def get_config(config=None):
# 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)
-
- # We can not merge defaults for tagNodes - those need to be blended in
- # per tagNode instance
- if 'listen_address' in default_values:
- del default_values['listen_address']
- if 'community' in default_values:
- del default_values['community']
- if 'trap_target' in default_values:
- del default_values['trap_target']
- if 'v3' in default_values:
- del default_values['v3']
- snmp = dict_merge(default_values, snmp)
+ snmp = conf.merge_defaults(snmp, recursive=True)
if 'listen_address' in snmp:
- default_values = defaults(base + ['listen-address'])
- for address in snmp['listen_address']:
- snmp['listen_address'][address] = dict_merge(
- default_values, snmp['listen_address'][address])
-
# Always listen on localhost if an explicit address has been configured
# This is a safety measure to not end up with invalid listen addresses
# that are not configured on this system. See https://vyos.dev/T850
@@ -101,41 +83,6 @@ def get_config(config=None):
tmp = {'::1': {'port': '161'}}
snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
- if 'community' in snmp:
- default_values = defaults(base + ['community'])
- if 'network' in default_values:
- # convert multiple default networks to list
- default_values['network'] = default_values['network'].split()
- for community in snmp['community']:
- snmp['community'][community] = dict_merge(
- default_values, snmp['community'][community])
-
- if 'trap_target' in snmp:
- default_values = defaults(base + ['trap-target'])
- for trap in snmp['trap_target']:
- snmp['trap_target'][trap] = dict_merge(
- default_values, snmp['trap_target'][trap])
-
- if 'v3' in snmp:
- default_values = defaults(base + ['v3'])
- # tagNodes need to be merged in individually later on
- for tmp in ['user', 'group', 'trap_target']:
- del default_values[tmp]
- snmp['v3'] = dict_merge(default_values, snmp['v3'])
-
- for user_group in ['user', 'group']:
- if user_group in snmp['v3']:
- default_values = defaults(base + ['v3', user_group])
- for tmp in snmp['v3'][user_group]:
- snmp['v3'][user_group][tmp] = dict_merge(
- default_values, snmp['v3'][user_group][tmp])
-
- if 'trap_target' in snmp['v3']:
- default_values = defaults(base + ['v3', 'trap-target'])
- for trap in snmp['v3']['trap_target']:
- snmp['v3']['trap_target'][trap] = dict_merge(
- default_values, snmp['v3']['trap_target'][trap])
-
return snmp
def verify(snmp):
diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 3b63fcb7d..ee5e1eca2 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -21,12 +21,10 @@ from syslog import syslog
from syslog import LOG_INFO
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.utils.process import call
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -57,8 +55,8 @@ def get_config(config=None):
# 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)
- ssh = dict_merge(default_values, ssh)
+ ssh = conf.merge_defaults(ssh, recursive=True)
+
# pass config file path - used in override template
ssh['config_file'] = config_file
diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py
index c89267afc..9ed34c735 100755
--- a/src/conf_mode/system-ip.py
+++ b/src/conf_mode/system-ip.py
@@ -24,7 +24,6 @@ from vyos.utils.process import call
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.system import sysctl_write
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -37,11 +36,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'ip']
- opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- opt = dict_merge(default_values, opt)
+ opt = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
# When working with FRR we need to know the corresponding address-family
opt['afi'] = 'ip'
diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py
index 22210c27a..8a4df11fa 100755
--- a/src/conf_mode/system-ipv6.py
+++ b/src/conf_mode/system-ipv6.py
@@ -24,7 +24,6 @@ from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.system import sysctl_write
from vyos.utils.file import write_file
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -37,12 +36,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'ipv6']
- opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # 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)
- opt = dict_merge(default_values, opt)
+ opt = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
# When working with FRR we need to know the corresponding address-family
opt['afi'] = 'ipv6'
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 82941e0c0..02c97afaa 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -24,7 +24,6 @@ from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configverify import verify_vrf
from vyos.defaults import directories
from vyos.template import render
@@ -35,7 +34,6 @@ from vyos.utils.process import call
from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from vyos.utils.process import DEVNULL
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -93,7 +91,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'login']
login = conf.get_config_dict(base, key_mangling=('-', '_'),
- no_tag_node_value_mangle=True, get_first_key=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
# users no longer existing in the running configuration need to be deleted
local_users = get_local_users()
@@ -101,27 +101,9 @@ def get_config(config=None):
if 'user' in login:
cli_users = list(login['user'])
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- default_values = defaults(base + ['user'])
- for user in login['user']:
- login['user'][user] = dict_merge(default_values, login['user'][user])
-
- # Add TACACS global defaults
- if 'tacacs' in login:
- default_values = defaults(base + ['tacacs'])
- if 'server' in default_values:
- del default_values['server']
- login['tacacs'] = dict_merge(default_values, login['tacacs'])
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- for backend in ['radius', 'tacacs']:
- default_values = defaults(base + [backend, 'server'])
- for server in dict_search(f'{backend}.server', login) or []:
- login[backend]['server'][server] = dict_merge(default_values,
- login[backend]['server'][server])
-
+ # prune TACACS global defaults if not set by user
+ if login.from_defaults(['tacacs']):
+ del login['tacacs']
# create a list of all users, cli and users
all_users = list(set(local_users + cli_users))
diff --git a/src/conf_mode/system-logs.py b/src/conf_mode/system-logs.py
index 12145d641..8ad4875d4 100755
--- a/src/conf_mode/system-logs.py
+++ b/src/conf_mode/system-logs.py
@@ -19,11 +19,9 @@ from sys import exit
from vyos import ConfigError
from vyos import airbag
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.logger import syslog
from vyos.template import render
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
airbag.enable()
# path to logrotate configs
@@ -38,11 +36,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'logs']
- default_values = defaults(base)
- logs_config = conf.get_config_dict(base,
- key_mangling=('-', '_'),
- get_first_key=True)
- logs_config = dict_merge(default_values, logs_config)
+ logs_config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return logs_config
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index 917013651..d92121b3d 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -21,14 +21,12 @@ from sys import exit
from time import sleep
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configverify import verify_source_interface
from vyos.template import render
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
from vyos.utils.network import is_intf_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -48,12 +46,9 @@ def get_config(config=None):
else:
conf = Config()
base = ['system', 'option']
- options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- # 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)
- options = dict_merge(default_values, options)
+ options = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return options
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 19c87bcee..07fbb0734 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -19,12 +19,10 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.utils.process import call
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -50,43 +48,9 @@ def get_config(config=None):
tmp = is_node_changed(conf, base + ['vrf'])
if tmp: syslog.update({'restart_required': {}})
- # 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)
- # XXX: some syslog default values can not be merged here (originating from
- # a tagNode - remove and add them later per individual tagNode instance
- if 'console' in default_values:
- del default_values['console']
- for entity in ['global', 'user', 'host', 'file']:
- if entity in default_values:
- del default_values[entity]
-
- syslog = dict_merge(default_values, syslog)
-
- # XXX: add defaults for "console" tree
- if 'console' in syslog and 'facility' in syslog['console']:
- default_values = defaults(base + ['console', 'facility'])
- for facility in syslog['console']['facility']:
- syslog['console']['facility'][facility] = dict_merge(default_values,
- syslog['console']['facility'][facility])
-
- # XXX: add defaults for "host" tree
- for syslog_type in ['host', 'user', 'file']:
- # Bail out early if there is nothing to do
- if syslog_type not in syslog:
- continue
-
- default_values_host = defaults(base + [syslog_type])
- if 'facility' in default_values_host:
- del default_values_host['facility']
-
- for tmp, tmp_config in syslog[syslog_type].items():
- syslog[syslog_type][tmp] = dict_merge(default_values_host, syslog[syslog_type][tmp])
- if 'facility' in tmp_config:
- default_values_facility = defaults(base + [syslog_type, 'facility'])
- for facility in tmp_config['facility']:
- syslog[syslog_type][tmp]['facility'][facility] = dict_merge(default_values_facility,
- syslog[syslog_type][tmp]['facility'][facility])
+ syslog = conf.merge_defaults(syslog, recursive=True)
+ if syslog.from_defaults(['global']):
+ del syslog['global']
return syslog
diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py
index 87d587959..ebf9a113b 100755
--- a/src/conf_mode/system_console.py
+++ b/src/conf_mode/system_console.py
@@ -19,12 +19,10 @@ import re
from pathlib import Path
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.utils.process import call
from vyos.utils.file import read_file
from vyos.utils.file import write_file
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -45,16 +43,12 @@ def get_config(config=None):
if 'device' not in console:
return console
- # convert CLI values to system values
- default_values = defaults(base + ['device'])
for device, device_config in console['device'].items():
if 'speed' not in device_config and device.startswith('hvc'):
# XEN console has a different default console speed
console['device'][device]['speed'] = 38400
- else:
- # Merge in XML defaults - the proper way to do it
- console['device'][device] = dict_merge(default_values,
- console['device'][device])
+
+ console = conf.merge_defaults(console, recursive=True)
return console
diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py
index eae869a6d..2df1bbb7a 100755
--- a/src/conf_mode/system_sflow.py
+++ b/src/conf_mode/system_sflow.py
@@ -19,11 +19,9 @@ import os
from sys import exit
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -42,26 +40,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- sflow = conf.get_config_dict(base,
- key_mangling=('-', '_'),
- get_first_key=True)
-
- # 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)
-
- sflow = dict_merge(default_values, sflow)
-
- # Ignore default XML values if config doesn't exists
- # Delete key from dict
- if 'port' in sflow['server']:
- del sflow['server']['port']
-
- # Set default values per server
- if 'server' in sflow:
- for server in sflow['server']:
- default_values = defaults(base + ['server'])
- sflow['server'][server] = dict_merge(default_values, sflow['server'][server])
+ sflow = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return sflow
diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py
index 32882fc12..3ad346e2e 100755
--- a/src/conf_mode/tftp_server.py
+++ b/src/conf_mode/tftp_server.py
@@ -24,14 +24,12 @@ 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
from vyos.template import render
from vyos.template import is_ipv4
from vyos.utils.process import call
from vyos.utils.permission import chmod_755
from vyos.utils.network import is_addr_assigned
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -48,11 +46,9 @@ def get_config(config=None):
if not conf.exists(base):
return None
- tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- tftpd = dict_merge(default_values, tftpd)
+ tftpd = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return tftpd
def verify(tftpd):
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 9a27a44bf..fa271cbdb 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -27,7 +27,6 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
-from vyos.configdict import dict_merge
from vyos.defaults import directories
from vyos.ifconfig import Interface
from vyos.pki import encode_public_key
@@ -45,7 +44,6 @@ from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
from vyos.utils.process import call
from vyos.utils.process import run
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -84,88 +82,23 @@ def get_config(config=None):
# retrieve common dictionary keys
ipsec = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
-
- # 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)
- # XXX: T2665: we must safely remove default values for tag nodes, those are
- # added in a more fine grained way later on
- del default_values['esp_group']
- del default_values['ike_group']
- del default_values['remote_access']
- del default_values['site_to_site']
- ipsec = dict_merge(default_values, ipsec)
-
- if 'esp_group' in ipsec:
- default_values = defaults(base + ['esp-group'])
- for group in ipsec['esp_group']:
- ipsec['esp_group'][group] = dict_merge(default_values,
- ipsec['esp_group'][group])
- if 'ike_group' in ipsec:
- default_values = defaults(base + ['ike-group'])
- # proposal is a tag node which may come with individual defaults per node
- if 'proposal' in default_values:
- del default_values['proposal']
-
- for group in ipsec['ike_group']:
- ipsec['ike_group'][group] = dict_merge(default_values,
- ipsec['ike_group'][group])
-
- if 'proposal' in ipsec['ike_group'][group]:
- default_values = defaults(base + ['ike-group', 'proposal'])
- for proposal in ipsec['ike_group'][group]['proposal']:
- ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values,
- ipsec['ike_group'][group]['proposal'][proposal])
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if dict_search('remote_access.connection', ipsec):
- default_values = defaults(base + ['remote-access', 'connection'])
- for rw in ipsec['remote_access']['connection']:
- ipsec['remote_access']['connection'][rw] = dict_merge(default_values,
- ipsec['remote_access']['connection'][rw])
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if dict_search('remote_access.radius.server', ipsec):
- # Fist handle the "base" stuff like RADIUS timeout
- default_values = defaults(base + ['remote-access', 'radius'])
- if 'server' in default_values:
- del default_values['server']
- ipsec['remote_access']['radius'] = dict_merge(default_values,
- ipsec['remote_access']['radius'])
-
- # Take care about individual RADIUS servers implemented as tagNodes - this
- # requires special treatment
- default_values = defaults(base + ['remote-access', 'radius', 'server'])
- for server in ipsec['remote_access']['radius']['server']:
- ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values,
- ipsec['remote_access']['radius']['server'][server])
-
- # XXX: T2665: we can not safely rely on the defaults() when there are
- # tagNodes in place, it is better to blend in the defaults manually.
- if dict_search('site_to_site.peer', ipsec):
- default_values = defaults(base + ['site-to-site', 'peer'])
- for peer in ipsec['site_to_site']['peer']:
- ipsec['site_to_site']['peer'][peer] = dict_merge(default_values,
- ipsec['site_to_site']['peer'][peer])
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
ipsec['dhcp_no_address'] = {}
ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes
ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel'])
ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
if tmp:
- ipsec['l2tp'] = tmp
- l2tp_defaults = defaults(l2tp_base)
- ipsec['l2tp'] = dict_merge(l2tp_defaults, ipsec['l2tp'])
+ ipsec['l2tp'] = conf.merge_defaults(tmp, recursive=True)
ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address'])
ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024'
ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1'
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index e82862fa3..a039172c4 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -19,7 +19,6 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import dict_merge
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
@@ -28,7 +27,6 @@ from vyos.utils.network import check_port_availability
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_listen_port_bind_service
from vyos.utils.dict import dict_search
-from vyos.xml import defaults
from vyos import ConfigError
from passlib.hash import sha512_crypt
from time import sleep
@@ -47,66 +45,6 @@ radius_servers = cfg_dir + '/radius_servers'
def get_hash(password):
return sha512_crypt.hash(password)
-
-
-def _default_dict_cleanup(origin: dict, default_values: dict) -> dict:
- """
- https://vyos.dev/T2665
- Clear unnecessary key values in merged config by dict_merge function
- :param origin: config
- :type origin: dict
- :param default_values: default values
- :type default_values: dict
- :return: merged dict
- :rtype: dict
- """
- if 'mode' in origin["authentication"] and "local" in \
- origin["authentication"]["mode"]:
- del origin['authentication']['local_users']['username']['otp']
- if not origin["authentication"]["local_users"]["username"]:
- raise ConfigError(
- 'Openconnect authentication mode local requires at least one user')
- default_ocserv_usr_values = \
- default_values['authentication']['local_users']['username']['otp']
- for user, params in origin['authentication']['local_users'][
- 'username'].items():
- # Not every configuration requires OTP settings
- if origin['authentication']['local_users']['username'][user].get(
- 'otp'):
- origin['authentication']['local_users']['username'][user][
- 'otp'] = dict_merge(default_ocserv_usr_values,
- origin['authentication'][
- 'local_users']['username'][user][
- 'otp'])
-
- if 'mode' in origin["authentication"] and "radius" in \
- origin["authentication"]["mode"]:
- del origin['authentication']['radius']['server']['port']
- if not origin["authentication"]['radius']['server']:
- raise ConfigError(
- 'Openconnect authentication mode radius requires at least one RADIUS server')
- default_values_radius_port = \
- default_values['authentication']['radius']['server']['port']
- for server, params in origin['authentication']['radius'][
- 'server'].items():
- if 'port' not in params:
- params['port'] = default_values_radius_port
-
- if 'mode' in origin["accounting"] and "radius" in \
- origin["accounting"]["mode"]:
- del origin['accounting']['radius']['server']['port']
- if not origin["accounting"]['radius']['server']:
- raise ConfigError(
- 'Openconnect accounting mode radius requires at least one RADIUS server')
- default_values_radius_port = \
- default_values['accounting']['radius']['server']['port']
- for server, params in origin['accounting']['radius'][
- 'server'].items():
- if 'port' not in params:
- params['port'] = default_values_radius_port
- return origin
-
-
def get_config(config=None):
if config:
conf = config
@@ -116,16 +54,14 @@ def get_config(config=None):
if not conf.exists(base):
return None
- ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://vyos.dev/T2665
- ocserv = _default_dict_cleanup(ocserv, default_values)
+ ocserv = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+
if ocserv:
ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ no_tag_node_value_mangle=True,
+ get_first_key=True)
return ocserv
@@ -142,6 +78,8 @@ def verify(ocserv):
# Check accounting
if "accounting" in ocserv:
if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]:
+ if not origin["accounting"]['radius']['server']:
+ raise ConfigError('Openconnect accounting mode radius requires at least one RADIUS server')
if "authentication" not in ocserv or "mode" not in ocserv["authentication"]:
raise ConfigError('Accounting depends on OpenConnect authentication configuration')
elif "radius" not in ocserv["authentication"]["mode"]:
@@ -150,9 +88,13 @@ def verify(ocserv):
# Check authentication
if "authentication" in ocserv:
if "mode" in ocserv["authentication"]:
- if "local" in ocserv["authentication"]["mode"]:
- if "radius" in ocserv["authentication"]["mode"]:
+ if ("local" in ocserv["authentication"]["mode"] and
+ "radius" in ocserv["authentication"]["mode"]):
raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration')
+ if "radius" in ocserv["authentication"]["mode"]:
+ if not ocserv["authentication"]['radius']['server']:
+ raise ConfigError('Openconnect authentication mode radius requires at least one RADIUS server')
+ if "local" in ocserv["authentication"]["mode"]:
if not ocserv["authentication"]["local_users"]:
raise ConfigError('openconnect mode local required at least one user')
if not ocserv["authentication"]["local_users"]["username"]:
diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py
index 80ce1e8e3..82c2f236e 100755
--- a/src/conf_mode/vpp.py
+++ b/src/conf_mode/vpp.py
@@ -22,7 +22,6 @@ from re import search as re_search, MULTILINE as re_M
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
-from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.ifconfig import Section
from vyos.utils.boot import boot_configuration_complete
@@ -31,7 +30,6 @@ from vyos.utils.process import rc_cmd
from vyos.utils.system import sysctl_read
from vyos.utils.system import sysctl_apply
from vyos.template import render
-from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -94,28 +92,18 @@ def get_config(config=None):
if not conf.exists(base):
return {'removed_ifaces': removed_ifaces}
- config = conf.get_config_dict(base,
+ config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
get_first_key=True,
- key_mangling=('-', '_'),
- no_tag_node_value_mangle=True)
-
- # 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)
- if 'interface' in default_values:
- del default_values['interface']
- config = dict_merge(default_values, config)
+ with_recursive_defaults=True)
if 'interface' in config:
for iface, iface_config in config['interface'].items():
- default_values_iface = defaults(base + ['interface'])
- config['interface'][iface] = dict_merge(default_values_iface, config['interface'][iface])
# add an interface to a list of interfaces that need
# to be reinitialized after the commit
set_dependents('ethernet', conf, iface)
- # Get PCI address auto
- for iface, iface_config in config['interface'].items():
+ # Get PCI address auto
if iface_config['pci'] == 'auto':
config['interface'][iface]['pci'] = _get_pci_address_by_interface(iface)
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index be867b208..37625142c 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -28,6 +28,8 @@ from vyos.template import render
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vrf_members
+from vyos.utils.network import interface_exists
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import popen
@@ -143,7 +145,7 @@ def verify(vrf):
raise ConfigError(f'VRF "{name}" table id is mandatory!')
# routing table id can't be changed - OS restriction
- if os.path.isdir(f'/sys/class/net/{name}'):
+ if interface_exists(name):
tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
if tmp and tmp != vrf_config['table']:
raise ConfigError(f'VRF "{name}" table id modification not possible!')
@@ -195,12 +197,23 @@ def apply(vrf):
sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all)
for tmp in (dict_search('vrf_remove', vrf) or []):
- if os.path.isdir(f'/sys/class/net/{tmp}'):
- call(f'ip link delete dev {tmp}')
+ if interface_exists(tmp):
+ # T5492: deleting a VRF instance may leafe processes running
+ # (e.g. dhclient) as there is a depedency ordering issue in the CLI.
+ # We need to ensure that we stop the dhclient processes first so
+ # a proper DHCLP RELEASE message is sent
+ for interface in get_vrf_members(tmp):
+ vrf_iface = Interface(interface)
+ vrf_iface.set_dhcp(False)
+ vrf_iface.set_dhcpv6(False)
+
# Remove nftables conntrack zone map item
nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}'
cmd(f'nft {nft_del_element}')
+ # Delete the VRF Kernel interface
+ call(f'ip link delete dev {tmp}')
+
if 'name' in vrf:
# Separate VRFs in conntrack table
# check if table already exists
@@ -245,7 +258,7 @@ def apply(vrf):
for name, config in vrf['name'].items():
table = config['table']
- if not os.path.isdir(f'/sys/class/net/{name}'):
+ if not interface_exists(name):
# For each VRF apart from your default context create a VRF
# interface with a separate routing table
call(f'ip link add {name} type vrf table {table}')
diff --git a/src/etc/netplug/linkdown.d/dhclient b/src/etc/netplug/linkdown.d/dhclient
deleted file mode 100755
index 555ff9134..000000000
--- a/src/etc/netplug/linkdown.d/dhclient
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/perl
-#
-# Module: dhclient
-#
-# **** License ****
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 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.
-#
-# A copy of the GNU General Public License is available as
-# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
-# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
-# You can also obtain it by writing to the Free Software Foundation,
-# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
-# MA 02110-1301, USA.
-#
-# This code was originally developed by Vyatta, Inc.
-# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc.
-# All Rights Reserved.
-#
-# Author: Mohit Mehta
-# Date: November 2008
-# Description: Script to release lease on link down
-#
-# **** End License ****
-#
-
-use lib "/opt/vyatta/share/perl5/";
-use Vyatta::Config;
-use Vyatta::Misc;
-
-use strict;
-use warnings;
-
-sub stop_dhclient {
- my $intf = shift;
- my $dhcp_daemon = '/sbin/dhclient';
- my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf);
- my $release_cmd = "sudo $dhcp_daemon -q -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file -r $intf 2> /dev/null;";
- $release_cmd .= "sudo rm -f $intf_process_id_file 2> /dev/null";
- system ($release_cmd);
-}
-
-
-#
-# main
-#
-
-my $dev=shift;
-
-# only do this if interface is configured to use dhcp for getting IP address
-if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) {
- # do a dhcp lease release for interface
- stop_dhclient($dev);
-}
-
-exit 0;
-
-# end of file
-
diff --git a/src/etc/netplug/linkup.d/dhclient b/src/etc/netplug/linkup.d/dhclient
deleted file mode 100755
index 8e50715fd..000000000
--- a/src/etc/netplug/linkup.d/dhclient
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/perl
-#
-# Module: dhclient
-#
-# **** License ****
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 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.
-#
-# A copy of the GNU General Public License is available as
-# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
-# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
-# You can also obtain it by writing to the Free Software Foundation,
-# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
-# MA 02110-1301, USA.
-#
-# This code was originally developed by Vyatta, Inc.
-# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc.
-# All Rights Reserved.
-#
-# Author: Mohit Mehta
-# Date: November 2008
-# Description: Script to renew lease on link up
-#
-# **** End License ****
-#
-
-use lib "/opt/vyatta/share/perl5/";
-use Vyatta::Config;
-use Vyatta::Misc;
-
-use strict;
-use warnings;
-
-sub run_dhclient {
- my $intf = shift;
- my $dhcp_daemon = '/sbin/dhclient';
- my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf);
- my $cmd = "sudo $dhcp_daemon -pf $intf_process_id_file -x $intf 2> /dev/null; sudo rm -f $intf_process_id_file 2> /dev/null;";
- $cmd .= "sudo $dhcp_daemon -q -nw -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file $intf 2> /dev/null &";
- system ($cmd);
-}
-
-#
-# main
-#
-
-my $dev=shift;
-
-# only do this if interface is configured to use dhcp for getting IP address
-if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) {
- # do a dhcp lease renew for interface
- run_dhclient($dev);
-}
-
-exit 0;
-
-# end of file
-
diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper
new file mode 100755
index 000000000..9c59c58ad
--- /dev/null
+++ b/src/etc/netplug/linkup.d/vyos-python-helper
@@ -0,0 +1,4 @@
+#!/bin/sh
+PYTHON3=$(which python3)
+# Call the real python script and forward commandline arguments
+$PYTHON3 /etc/netplug/vyos-netplug-dhcp-client "${@:1}"
diff --git a/src/etc/netplug/netplug b/src/etc/netplug/netplug
new file mode 100755
index 000000000..60b65e8c9
--- /dev/null
+++ b/src/etc/netplug/netplug
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+dev="$1"
+action="$2"
+
+case "$action" in
+in)
+ run-parts --arg $dev --arg in /etc/netplug/linkup.d
+ ;;
+out)
+ run-parts --arg $dev --arg out /etc/netplug/linkdown.d
+ ;;
+
+# probe loads and initialises the driver for the interface and brings the
+# interface into the "up" state, so that it can generate netlink(7) events.
+# This interferes with "admin down" for an interface. Thus, commented out. An
+# "admin up" is treated as a "link up" and thus, "link up" action is executed.
+# To execute "link down" action on "admin down", run appropriate script in
+# /etc/netplug/linkdown.d
+#probe)
+# ;;
+
+*)
+ exit 1
+ ;;
+esac
diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf
new file mode 100644
index 000000000..7da3c67e8
--- /dev/null
+++ b/src/etc/netplug/netplugd.conf
@@ -0,0 +1,4 @@
+eth*
+br*
+bond*
+wlan*
diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client
new file mode 100755
index 000000000..55d15a163
--- /dev/null
+++ b/src/etc/netplug/vyos-netplug-dhcp-client
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from time import sleep
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.ifconfig import Section
+from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.process import call
+from vyos import airbag
+airbag.enable()
+
+if len(sys.argv) < 3:
+ airbag.noteworthy("Must specify both interface and link status!")
+ sys.exit(1)
+
+if not boot_configuration_complete():
+ airbag.noteworthy("System bootup not yet finished...")
+ sys.exit(1)
+
+while commit_in_progress():
+ sleep(1)
+
+interface = sys.argv[1]
+in_out = sys.argv[2]
+config = ConfigTreeQuery()
+
+interface_path = ['interfaces'] + Section.get_config_path(interface).split()
+
+for _, interface_config in config.get_config_dict(interface_path).items():
+ # Bail out early if we do not have an IP address configured
+ if 'address' not in interface_config:
+ continue
+ # Bail out early if interface ist administrative down
+ if 'disable' in interface_config:
+ continue
+ systemd_action = 'start'
+ if in_out == 'out':
+ systemd_action = 'stop'
+ # Start/Stop DHCP service
+ if 'dhcp' in interface_config['address']:
+ call(f'systemctl {systemd_action} dhclient@{interface}.service')
+ # Start/Stop DHCPv6 service
+ if 'dhcpv6' in interface_config['address']:
+ call(f'systemctl {systemd_action} dhcp6c@{interface}.service')
diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py
index 2036ca72e..7e2fe2462 100755
--- a/src/helpers/vyos-domain-resolver.py
+++ b/src/helpers/vyos-domain-resolver.py
@@ -26,7 +26,7 @@ from vyos.utils.commit import commit_in_progress
from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
from vyos.utils.process import run
-from vyos.xml import defaults
+from vyos.xml_ref import get_defaults
base = ['firewall']
timeout = 300
@@ -49,13 +49,7 @@ def get_config(conf):
firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- default_values = defaults(base)
- for tmp in ['name', 'ipv6_name']:
- if tmp in default_values:
- del default_values[tmp]
-
- if 'zone' in default_values:
- del default_values['zone']
+ default_values = get_defaults(base, get_first_key=True)
firewall = dict_merge(default_values, firewall)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 7b752b84b..96f163213 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -101,6 +101,16 @@ load_bootfile ()
)
}
+# restore if missing pre-config script
+restore_if_missing_preconfig_script ()
+{
+ if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then
+ cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
+ chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
+ chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
+ fi
+}
+
# execute the pre-config script
run_preconfig_script ()
{
@@ -109,6 +119,16 @@ run_preconfig_script ()
fi
}
+# restore if missing post-config script
+restore_if_missing_postconfig_script ()
+{
+ if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then
+ cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
+ chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
+ chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
+ fi
+}
+
# execute the post-config scripts
run_postconfig_scripts ()
{
@@ -360,6 +380,8 @@ start ()
log_daemon_msg "Starting VyOS router"
disabled migrate || migrate_bootfile
+ restore_if_missing_preconfig_script
+
run_preconfig_script
run_postupgrade_script
@@ -384,6 +406,8 @@ start ()
telinit q
chmod g-w,o-w /
+ restore_if_missing_postconfig_script
+
run_postconfig_scripts
}
diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11
new file mode 100755
index 000000000..716c5a240
--- /dev/null
+++ b/src/migration-scripts/firewall/10-to-11
@@ -0,0 +1,374 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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/>.
+
+# T5160: Firewall re-writing
+
+# cli changes from:
+# set firewall name <name> ...
+# set firewall ipv6-name <name> ...
+# To
+# set firewall ipv4 name <name>
+# set firewall ipv6 name <name>
+
+## Also from 'firewall interface' removed.
+## in and out:
+ # set firewall interface <iface> [in|out] [name | ipv6-name] <name>
+ # To
+ # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface>
+ # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump
+ # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name>
+## local:
+ # set firewall interface <iface> local [name | ipv6-name] <name>
+ # To
+ # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface>
+ # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump
+ # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name>
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['firewall']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+### Migration of state policies
+if config.exists(base + ['state-policy']):
+ for family in ['ipv4', 'ipv6']:
+ for hook in ['forward', 'input', 'output']:
+ for priority in ['filter']:
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + [family, hook, priority, 'default-action'], value='accept')
+ position = 1
+ for state in config.list_nodes(base + ['state-policy']):
+ action = config.return_value(base + ['state-policy', state, 'action'])
+ config.set(base + [family, hook, priority, 'rule'])
+ config.set_tag(base + [family, hook, priority, 'rule'])
+ config.set(base + [family, hook, priority, 'rule', position, 'state', state], value='enable')
+ config.set(base + [family, hook, priority, 'rule', position, 'action'], value=action)
+ position = position + 1
+ config.delete(base + ['state-policy'])
+
+## migration of global options:
+for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians',
+ 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']:
+ if config.exists(base + [option]):
+ if option != 'config-trap':
+ val = config.return_value(base + [option])
+ config.set(base + ['global-options', option], value=val)
+ config.delete(base + [option])
+
+### Migration of firewall name and ipv6-name
+if config.exists(base + ['name']):
+ config.set(['firewall', 'ipv4', 'name'])
+ config.set_tag(['firewall', 'ipv4', 'name'])
+
+ for ipv4name in config.list_nodes(base + ['name']):
+ config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name])
+ config.delete(base + ['name'])
+
+if config.exists(base + ['ipv6-name']):
+ config.set(['firewall', 'ipv6', 'name'])
+ config.set_tag(['firewall', 'ipv6', 'name'])
+
+ for ipv6name in config.list_nodes(base + ['ipv6-name']):
+ config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name])
+ config.delete(base + ['ipv6-name'])
+
+### Migration of firewall interface
+if config.exists(base + ['interface']):
+ fwd_ipv4_rule = 5
+ inp_ipv4_rule = 5
+ fwd_ipv6_rule = 5
+ inp_ipv6_rule = 5
+ for iface in config.list_nodes(base + ['interface']):
+ for direction in ['in', 'out', 'local']:
+ if config.exists(base + ['interface', iface, direction]):
+ if config.exists(base + ['interface', iface, direction, 'name']):
+ target = config.return_value(base + ['interface', iface, direction, 'name'])
+ if direction == 'in':
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
+ new_base = base + ['ipv4', 'forward', 'filter', 'rule']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
+ config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
+ fwd_ipv4_rule = fwd_ipv4_rule + 5
+ elif direction == 'out':
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
+ new_base = base + ['ipv4', 'forward', 'filter', 'rule']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
+ config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
+ fwd_ipv4_rule = fwd_ipv4_rule + 5
+ else:
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept')
+ new_base = base + ['ipv4', 'input', 'filter', 'rule']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [inp_ipv4_rule, 'action'], value='jump')
+ config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target)
+ inp_ipv4_rule = inp_ipv4_rule + 5
+
+ if config.exists(base + ['interface', iface, direction, 'ipv6-name']):
+ target = config.return_value(base + ['interface', iface, direction, 'ipv6-name'])
+ if direction == 'in':
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
+ new_base = base + ['ipv6', 'forward', 'filter', 'rule']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
+ config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
+ fwd_ipv6_rule = fwd_ipv6_rule + 5
+ elif direction == 'out':
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
+ new_base = base + ['ipv6', 'forward', 'filter', 'rule']
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
+ config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
+ fwd_ipv6_rule = fwd_ipv6_rule + 5
+ else:
+ new_base = base + ['ipv6', 'input', 'filter', 'rule']
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept')
+ config.set(new_base)
+ config.set_tag(new_base)
+ config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
+ config.set(new_base + [inp_ipv6_rule, 'action'], value='jump')
+ config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target)
+ inp_ipv6_rule = inp_ipv6_rule + 5
+
+ config.delete(base + ['interface'])
+
+
+### Migration of zones:
+### User interface groups
+if config.exists(base + ['zone']):
+ inp_ipv4_rule = 101
+ inp_ipv6_rule = 101
+ fwd_ipv4_rule = 101
+ fwd_ipv6_rule = 101
+ out_ipv4_rule = 101
+ out_ipv6_rule = 101
+ local_zone = 'False'
+
+ for zone in config.list_nodes(base + ['zone']):
+ if config.exists(base + ['zone', zone, 'local-zone']):
+ local_zone = 'True'
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept')
+ config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept')
+ config.set(base + ['ipv4', 'output', 'filter', 'default-action'], value='accept')
+ config.set(base + ['ipv6', 'output', 'filter', 'default-action'], value='accept')
+ for from_zone in config.list_nodes(base + ['zone', zone, 'from']):
+ group_name = 'IG_' + from_zone
+ if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']):
+ # ipv4 input ruleset
+ target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name'])
+ config.set(base + ['ipv4', 'input', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'input', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value='jump')
+ config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
+ inp_ipv4_rule = inp_ipv4_rule + 5
+ if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']):
+ # ipv6 input ruleset
+ target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name'])
+ config.set(base + ['ipv6', 'input', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'input', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value='jump')
+ config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
+ inp_ipv6_rule = inp_ipv6_rule + 5
+
+ # Migrate: set firewall zone <zone> default-action <action>
+ # Options: drop or reject. If not specified, is drop
+ if config.exists(base + ['zone', zone, 'default-action']):
+ local_def_action = config.return_value(base + ['zone', zone, 'default-action'])
+ else:
+ local_def_action = 'drop'
+ config.set(base + ['ipv4', 'input', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'input', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value=local_def_action)
+ config.set(base + ['ipv6', 'input', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'input', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value=local_def_action)
+ if config.exists(base + ['zone', zone, 'enable-default-log']):
+ config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'log'], value='enable')
+ config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'log'], value='enable')
+
+ else:
+ # It's not a local zone
+ group_name = 'IG_' + zone
+ # Add default-action== accept for compatibility reasons:
+ config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
+ config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
+ # intra-filtering migration. By default accept
+ intra_zone_ipv4_action = 'accept'
+ intra_zone_ipv6_action = 'accept'
+
+ if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'action']):
+ intra_zone_ipv4_action = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'action'])
+ intra_zone_ipv6_action = intra_zone_ipv4_action
+ else:
+ if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']):
+ intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name'])
+ intra_zone_ipv4_action = 'jump'
+ if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']):
+ intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name'])
+ intra_zone_ipv6_action = 'jump'
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=intra_zone_ipv4_action)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=intra_zone_ipv6_action)
+ if intra_zone_ipv4_action == 'jump':
+ if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']):
+ intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name'])
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=intra_zone_ipv4_target)
+ if intra_zone_ipv6_action == 'jump':
+ if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']):
+ intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name'])
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=intra_zone_ipv6_target)
+ fwd_ipv4_rule = fwd_ipv4_rule + 5
+ fwd_ipv6_rule = fwd_ipv6_rule + 5
+
+ if config.exists(base + ['zone', zone, 'interface']):
+ # Create interface group IG_<zone>
+ group_name = 'IG_' + zone
+ config.set(base + ['group', 'interface-group'], value=group_name)
+ config.set_tag(base + ['group', 'interface-group'])
+ for iface in config.return_values(base + ['zone', zone, 'interface']):
+ config.set(base + ['group', 'interface-group', group_name, 'interface'], value=iface, replace=False)
+
+ if config.exists(base + ['zone', zone, 'from']):
+ for from_zone in config.list_nodes(base + ['zone', zone, 'from']):
+ from_group = 'IG_' + from_zone
+ if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']):
+ target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name'])
+ if config.exists(base + ['zone', from_zone, 'local-zone']):
+ # It's from LOCAL zone -> Output filtering
+ config.set(base + ['ipv4', 'output', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'output', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value='jump')
+ config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
+ out_ipv4_rule = out_ipv4_rule + 5
+ else:
+ # It's not LOCAL zone -> forward filtering
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=from_group)
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value='jump')
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=target_ipv4_chain)
+ fwd_ipv4_rule = fwd_ipv4_rule + 5
+ if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']):
+ target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name'])
+ if config.exists(base + ['zone', from_zone, 'local-zone']):
+ # It's from LOCAL zone -> Output filtering
+ config.set(base + ['ipv6', 'output', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'output', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value='jump')
+ config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
+ out_ipv6_rule = out_ipv6_rule + 5
+ else:
+ # It's not LOCAL zone -> forward filtering
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=from_group)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value='jump')
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=target_ipv6_chain)
+ fwd_ipv6_rule = fwd_ipv6_rule + 5
+
+ ## Now need to migrate: set firewall zone <zone> default-action <action> # action=drop if not specified.
+ if config.exists(base + ['zone', zone, 'default-action']):
+ def_action = config.return_value(base + ['zone', zone, 'default-action'])
+ else:
+ def_action = 'drop'
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=def_action)
+ description = 'zone_' + zone + ' default-action'
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'description'], value=description)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=def_action)
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'description'], value=description)
+
+ if config.exists(base + ['zone', zone, 'enable-default-log']):
+ config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'log'], value='enable')
+ config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'log'], value='enable')
+ fwd_ipv4_rule = fwd_ipv4_rule + 5
+ fwd_ipv6_rule = fwd_ipv6_rule + 5
+
+ # Migrate default-action (force to be drop in output chain) if local zone is defined
+ if local_zone == 'True':
+ # General drop in output change if needed
+ config.set(base + ['ipv4', 'output', 'filter', 'rule'])
+ config.set_tag(base + ['ipv4', 'output', 'filter', 'rule'])
+ config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value=local_def_action)
+ config.set(base + ['ipv6', 'output', 'filter', 'rule'])
+ config.set_tag(base + ['ipv6', 'output', 'filter', 'rule'])
+ config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value=local_def_action)
+
+ config.delete(base + ['zone'])
+
+###### END migration zones
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1) \ No newline at end of file
diff --git a/src/migration-scripts/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6
index 3fa07a0a1..e1eaea7a1 100755
--- a/src/migration-scripts/firewall/5-to-6
+++ b/src/migration-scripts/firewall/5-to-6
@@ -46,12 +46,54 @@ for interface in config.list_nodes(base):
if config.exists(base + [interface, 'adjust-mss']):
section = Section.section(interface)
tmp = config.return_value(base + [interface, 'adjust-mss'])
- config.set(['interfaces', section, interface, 'ip', 'adjust-mss'], value=tmp)
+
+ vlan = interface.split('.')
+ base_interface_path = ['interfaces', section, vlan[0]]
+
+ if len(vlan) == 1:
+ # Normal interface, no VLAN
+ config.set(base_interface_path + ['ip', 'adjust-mss'], value=tmp)
+ elif len(vlan) == 2:
+ # Regular VIF or VIF-S interface - we need to check the config
+ vif = vlan[1]
+ if config.exists(base_interface_path + ['vif', vif]):
+ config.set(base_interface_path + ['vif', vif, 'ip', 'adjust-mss'], value=tmp)
+ elif config.exists(base_interface_path + ['vif-s', vif]):
+ config.set(base_interface_path + ['vif-s', vif, 'ip', 'adjust-mss'], value=tmp)
+ elif len(vlan) == 3:
+ # VIF-S interface with VIF-C subinterface
+ vif_s = vlan[1]
+ vif_c = vlan[2]
+ config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ip', 'adjust-mss'], value=tmp)
+ config.set_tag(base_interface_path + ['vif-s'])
+ config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c'])
if config.exists(base + [interface, 'adjust-mss6']):
section = Section.section(interface)
tmp = config.return_value(base + [interface, 'adjust-mss6'])
- config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp)
+
+ vlan = interface.split('.')
+ base_interface_path = ['interfaces', section, vlan[0]]
+
+ if len(vlan) == 1:
+ # Normal interface, no VLAN
+ config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp)
+ elif len(vlan) == 2:
+ # Regular VIF or VIF-S interface - we need to check the config
+ vif = vlan[1]
+ if config.exists(base_interface_path + ['vif', vif]):
+ config.set(base_interface_path + ['vif', vif, 'ipv6', 'adjust-mss'], value=tmp)
+ config.set_tag(base_interface_path + ['vif'])
+ elif config.exists(base_interface_path + ['vif-s', vif]):
+ config.set(base_interface_path + ['vif-s', vif, 'ipv6', 'adjust-mss'], value=tmp)
+ config.set_tag(base_interface_path + ['vif-s'])
+ elif len(vlan) == 3:
+ # VIF-S interface with VIF-C subinterface
+ vif_s = vlan[1]
+ vif_c = vlan[2]
+ config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ipv6', 'adjust-mss'], value=tmp)
+ config.set_tag(base_interface_path + ['vif-s'])
+ config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c'])
config.delete(['firewall', 'options'])
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 8260bbb77..852a7248a 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -24,62 +24,27 @@ from vyos.config import Config
from vyos.utils.process import cmd
from vyos.utils.dict import dict_search_args
-def get_firewall_interfaces(firewall, name=None, ipv6=False):
- directions = ['in', 'out', 'local']
-
- if 'interface' in firewall:
- for ifname, if_conf in firewall['interface'].items():
- for direction in directions:
- if direction not in if_conf:
- continue
-
- fw_conf = if_conf[direction]
- name_str = f'({ifname},{direction})'
-
- if 'name' in fw_conf:
- fw_name = fw_conf['name']
-
- if not name:
- firewall['name'][fw_name]['interface'].append(name_str)
- elif not ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
-
- if 'ipv6_name' in fw_conf:
- fw_name = fw_conf['ipv6_name']
-
- if not name:
- firewall['ipv6_name'][fw_name]['interface'].append(name_str)
- elif ipv6 and name == fw_name:
- firewall['interface'].append(name_str)
-
- return firewall
-
-def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
+def get_config_firewall(conf, hook=None, priority=None, ipv6=False, interfaces=True):
config_path = ['firewall']
- if name:
- config_path += ['ipv6-name' if ipv6 else 'name', name]
+ if hook:
+ config_path += ['ipv6' if ipv6 else 'ipv4', hook]
+ if priority:
+ config_path += [priority]
firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
- if firewall and interfaces:
- if name:
- firewall['interface'] = {}
- else:
- if 'name' in firewall:
- for fw_name, name_conf in firewall['name'].items():
- name_conf['interface'] = []
- if 'ipv6_name' in firewall:
- for fw_name, name_conf in firewall['ipv6_name'].items():
- name_conf['interface'] = []
-
- get_firewall_interfaces(firewall, name, ipv6)
return firewall
-def get_nftables_details(name, ipv6=False):
+def get_nftables_details(hook, priority, ipv6=False):
suffix = '6' if ipv6 else ''
name_prefix = 'NAME6_' if ipv6 else 'NAME_'
- command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{name}'
+ if hook == 'name' or hook == 'ipv6-name':
+ command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}'
+ else:
+ up_hook = hook.upper()
+ command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{up_hook}_{priority}'
+
try:
results = cmd(command)
except:
@@ -87,7 +52,7 @@ def get_nftables_details(name, ipv6=False):
out = {}
for line in results.split('\n'):
- comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ comment_search = re.search(rf'{priority}[\- ](\d+|default-action)', line)
if not comment_search:
continue
@@ -102,18 +67,15 @@ def get_nftables_details(name, ipv6=False):
out[rule_id] = rule
return out
-def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
+def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_id=None):
ip_str = 'IPv6' if ipv6 else 'IPv4'
- print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
-
- if name_conf['interface']:
- print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
+ print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {priority}"\n')
- details = get_nftables_details(name, ipv6)
+ details = get_nftables_details(hook, priority, ipv6)
rows = []
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
+ if 'rule' in firewall_conf:
+ for rule_id, rule_conf in firewall_conf['rule'].items():
if single_rule_id and rule_id != single_rule_id:
continue
@@ -128,8 +90,8 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
row.append(rule_details['conditions'])
rows.append(row)
- if 'default_action' in name_conf and not single_rule_id:
- row = ['default', name_conf['default_action'], 'all']
+ if 'default_action' in firewall_conf and not single_rule_id:
+ row = ['default', firewall_conf['default_action'], 'all']
if 'default-action' in details:
rule_details = details['default-action']
row.append(rule_details.get('packets', 0))
@@ -140,18 +102,15 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']
print(tabulate.tabulate(rows, header) + '\n')
-def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=None):
+def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_rule_id=None):
ip_str = 'IPv6' if ipv6 else 'IPv4'
- print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
+ print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n')
- if name_conf['interface']:
- print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
-
- details = get_nftables_details(name, ipv6)
+ details = get_nftables_details(prior, ipv6)
rows = []
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
+ if 'rule' in prior_conf:
+ for rule_id, rule_conf in prior_conf['rule'].items():
if single_rule_id and rule_id != single_rule_id:
continue
@@ -174,7 +133,7 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=
row.append(dest_addr)
rows.append(row)
- if 'default_action' in name_conf and not single_rule_id:
+ if 'default_action' in prior_conf and not single_rule_id:
row = ['default']
if 'default-action' in details:
rule_details = details['default-action']
@@ -183,7 +142,7 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=
else:
row.append('0')
row.append('0')
- row.append(name_conf['default_action'])
+ row.append(prior_conf['default_action'])
row.append('0.0.0.0/0') # Source
row.append('0.0.0.0/0') # Dest
rows.append(row)
@@ -201,29 +160,47 @@ def show_firewall():
if not firewall:
return
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- output_firewall_name(name, name_conf, ipv6=False)
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ output_firewall_name(hook, prior, prior_conf, ipv6=False)
+
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ output_firewall_name(hook, prior, prior_conf, ipv6=True)
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- output_firewall_name(name, name_conf, ipv6=True)
+def show_firewall_family(family):
+ print(f'Rulesets {family} Information')
-def show_firewall_name(name, ipv6=False):
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ for hook, hook_conf in firewall[family].items():
+ for prior, prior_conf in firewall[family][hook].items():
+ if family == 'ipv6':
+ output_firewall_name(hook, prior, prior_conf, ipv6=True)
+ else:
+ output_firewall_name(hook, prior, prior_conf, ipv6=False)
+
+def show_firewall_name(hook, priority, ipv6=False):
print('Ruleset Information')
conf = Config()
- firewall = get_config_firewall(conf, name, ipv6)
+ firewall = get_config_firewall(conf, hook, priority, ipv6)
if firewall:
- output_firewall_name(name, firewall, ipv6)
+ output_firewall_name(hook, priority, firewall, ipv6)
-def show_firewall_rule(name, rule_id, ipv6=False):
+def show_firewall_rule(hook, priority, rule_id, ipv6=False):
print('Rule Information')
conf = Config()
- firewall = get_config_firewall(conf, name, ipv6)
+ firewall = get_config_firewall(conf, hook, priority, ipv6)
if firewall:
- output_firewall_name(name, firewall, ipv6, rule_id)
+ output_firewall_name(hook, priority, firewall, ipv6, rule_id)
def show_firewall_group(name=None):
conf = Config()
@@ -234,19 +211,32 @@ def show_firewall_group(name=None):
def find_references(group_type, group_name):
out = []
- for name_type in ['name', 'ipv6_name']:
- if name_type not in firewall:
- continue
- for name, name_conf in firewall[name_type].items():
- if 'rule' not in name_conf:
+ family = []
+ if group_type in ['address_group', 'network_group']:
+ family = ['ipv4']
+ elif group_type == 'ipv6_address_group':
+ family = ['ipv6']
+ group_type = 'address_group'
+ elif group_type == 'ipv6_network_group':
+ family = ['ipv6']
+ group_type = 'network_group'
+ else:
+ family = ['ipv4', 'ipv6']
+
+ for item in family:
+ for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']:
+ if name_type not in firewall[item]:
continue
- for rule_id, rule_conf in name_conf['rule'].items():
- source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
- dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
- if source_group and group_name == source_group:
- out.append(f'{name}-{rule_id}')
- elif dest_group and group_name == dest_group:
- out.append(f'{name}-{rule_id}')
+ for name, name_conf in firewall[item][name_type].items():
+ if 'rule' not in name_conf:
+ continue
+ for rule_id, rule_conf in name_conf['rule'].items():
+ source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
+ dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
+ if source_group and group_name == source_group:
+ out.append(f'{name}-{rule_id}')
+ elif dest_group and group_name == dest_group:
+ out.append(f'{name}-{rule_id}')
return out
header = ['Name', 'Type', 'References', 'Members']
@@ -284,28 +274,28 @@ def show_summary():
if not firewall:
return
- header = ['Ruleset Name', 'Description', 'References']
+ header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References']
v4_out = []
v6_out = []
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- description = name_conf.get('description', '')
- interfaces = ", ".join(name_conf['interface'])
- v4_out.append([name, description, interfaces])
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ description = prior_conf.get('description', '')
+ v4_out.append([hook, prior, description])
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- description = name_conf.get('description', '')
- interfaces = ", ".join(name_conf['interface'])
- v6_out.append([name, description, interfaces or 'N/A'])
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ description = prior_conf.get('description', '')
+ v6_out.append([hook, prior, description])
if v6_out:
- print('\nIPv6 name:\n')
+ print('\nIPv6 Ruleset:\n')
print(tabulate.tabulate(v6_out, header) + '\n')
if v4_out:
- print('\nIPv4 name:\n')
+ print('\nIPv4 Ruleset:\n')
print(tabulate.tabulate(v4_out, header) + '\n')
show_firewall_group()
@@ -319,18 +309,23 @@ def show_statistics():
if not firewall:
return
- if 'name' in firewall:
- for name, name_conf in firewall['name'].items():
- output_firewall_name_statistics(name, name_conf, ipv6=False)
+ if 'ipv4' in firewall:
+ for hook, hook_conf in firewall['ipv4'].items():
+ for prior, prior_conf in firewall['ipv4'][hook].items():
+ output_firewall_name_statistics(hook,prior, prior_conf, ipv6=False)
- if 'ipv6_name' in firewall:
- for name, name_conf in firewall['ipv6_name'].items():
- output_firewall_name_statistics(name, name_conf, ipv6=True)
+ if 'ipv6' in firewall:
+ for hook, hook_conf in firewall['ipv6'].items():
+ for prior, prior_conf in firewall['ipv6'][hook].items():
+ output_firewall_name_statistics(hook,prior, prior_conf, ipv6=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--action', help='Action', required=False)
parser.add_argument('--name', help='Firewall name', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--family', help='IP family', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--hook', help='Firewall hook', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--priority', help='Firewall priority', required=False, action='store', nargs='?', default='')
parser.add_argument('--rule', help='Firewall Rule ID', required=False)
parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
@@ -338,11 +333,13 @@ if __name__ == '__main__':
if args.action == 'show':
if not args.rule:
- show_firewall_name(args.name, args.ipv6)
+ show_firewall_name(args.hook, args.priority, args.ipv6)
else:
- show_firewall_rule(args.name, args.rule, args.ipv6)
+ show_firewall_rule(args.hook, args.priority, args.rule, args.ipv6)
elif args.action == 'show_all':
show_firewall()
+ elif args.action == 'show_family':
+ show_firewall_family(args.family)
elif args.action == 'show_group':
show_firewall_group(args.name)
elif args.action == 'show_statistics':
diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py
index 1edeb0045..8b3c45c7c 100755
--- a/src/op_mode/neighbor.py
+++ b/src/op_mode/neighbor.py
@@ -31,14 +31,11 @@ import sys
import typing
import vyos.opmode
+from vyos.utils.network import interface_exists
ArgFamily = typing.Literal['inet', 'inet6']
ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent']
-def interface_exists(interface):
- import os
- return os.path.exists(f'/sys/class/net/{interface}')
-
def get_raw_data(family, interface=None, state=None):
from json import loads
from vyos.utils.process import cmd
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 4c31291ad..35c7ce0e2 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -25,9 +25,8 @@ from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID
from vyos.config import Config
-from vyos.configquery import ConfigTreeQuery
-from vyos.configdict import dict_merge
from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters
+from vyos.pki import get_certificate_fingerprint
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
from vyos.pki import create_private_key
from vyos.pki import create_dh_parameters
@@ -38,21 +37,19 @@ from vyos.utils.io import ask_input
from vyos.utils.io import ask_yes_no
from vyos.utils.misc import install_into_config
from vyos.utils.process import cmd
-from vyos.xml import defaults
CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
# Helper Functions
-conf = ConfigTreeQuery()
+conf = Config()
def get_default_values():
# Fetch default x509 values
base = ['pki', 'x509', 'default']
x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
get_first_key=True,
- no_tag_node_value_mangle=True)
- default_values = defaults(base)
- x509_defaults = dict_merge(default_values, x509_defaults)
+ with_recursive_defaults=True)
return x509_defaults
@@ -916,6 +913,12 @@ def show_certificate(name=None, pem=False):
print("Certificates:")
print(tabulate.tabulate(data, headers))
+def show_certificate_fingerprint(name, hash):
+ cert = get_config_certificate(name=name)
+ cert = load_certificate(cert['certificate'])
+
+ print(get_certificate_fingerprint(cert, hash))
+
def show_crl(name=None, pem=False):
headers = ['CA Name', 'Updated', 'Revokes']
data = []
@@ -961,6 +964,7 @@ if __name__ == '__main__':
parser.add_argument('--sign', help='Sign certificate with specified CA', required=False)
parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true')
parser.add_argument('--pem', help='Output using PEM encoding', action='store_true')
+ parser.add_argument('--fingerprint', help='Show fingerprint and exit', action='store')
# SSH
parser.add_argument('--ssh', help='SSH Key', required=False)
@@ -1057,7 +1061,10 @@ if __name__ == '__main__':
if not conf.exists(['pki', 'certificate', cert_name]):
print(f'Certificate "{cert_name}" does not exist!')
exit(1)
- show_certificate(None if args.certificate == 'all' else args.certificate, args.pem)
+ if args.fingerprint is None:
+ show_certificate(None if args.certificate == 'all' else args.certificate, args.pem)
+ else:
+ show_certificate_fingerprint(args.certificate, args.fingerprint)
elif args.crl:
show_crl(None if args.crl == 'all' else args.crl, args.pem)
else:
diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py
index 415a5f72c..3771fb385 100755
--- a/src/op_mode/show_openconnect_otp.py
+++ b/src/op_mode/show_openconnect_otp.py
@@ -17,12 +17,11 @@
import argparse
import os
+from base64 import b32encode
from vyos.config import Config
-from vyos.xml import defaults
-from vyos.configdict import dict_merge
+from vyos.utils.dict import dict_search_args
from vyos.utils.process import popen
-from base64 import b32encode
otp_file = '/run/ocserv/users.oath'
@@ -33,7 +32,7 @@ def check_uname_otp(username):
config = Config()
base_key = ['vpn', 'openconnect', 'authentication', 'local-users', 'username', username, 'otp', 'key']
if not config.exists(base_key):
- return None
+ return False
return True
def get_otp_ocserv(username):
@@ -41,21 +40,21 @@ def get_otp_ocserv(username):
base = ['vpn', 'openconnect']
if not config.exists(base):
return None
- ocserv = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # 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)
- ocserv = dict_merge(default_values, ocserv)
- # workaround a "know limitation" - https://vyos.dev/T2665
- del ocserv['authentication']['local_users']['username']['otp']
- if not ocserv["authentication"]["local_users"]["username"]:
+
+ ocserv = config.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+
+ user_path = ['authentication', 'local_users', 'username']
+ users = dict_search_args(ocserv, *user_path)
+
+ if users is None:
return None
- default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp']
- for user, params in ocserv['authentication']['local_users']['username'].items():
- # Not every configuration requires OTP settings
- if ocserv['authentication']['local_users']['username'][user].get('otp'):
- ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])
- result = ocserv['authentication']['local_users']['username'][username]
+
+ # function is called conditionally, if check_uname_otp true, so username
+ # exists
+ result = users[username]
+
return result
def display_otp_ocserv(username, params, info):
@@ -101,8 +100,7 @@ if __name__ == '__main__':
parser.add_argument('--info', action="store", type=str, default='full', help='Wich information to display')
args = parser.parse_args()
- check_otp = check_uname_otp(args.user)
- if check_otp:
+ if check_uname_otp(args.user):
user_otp_params = get_otp_ocserv(args.user)
display_otp_ocserv(args.user, user_otp_params, args.info)
else:
diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py
index 1f0bbbaeb..51032a4b5 100755
--- a/src/op_mode/vrf.py
+++ b/src/op_mode/vrf.py
@@ -20,11 +20,11 @@ import sys
import typing
from tabulate import tabulate
+from vyos.utils.network import get_vrf_members
from vyos.utils.process import cmd
import vyos.opmode
-
def _get_raw_data(name=None):
"""
If vrf name is not set - get all VRFs
@@ -45,21 +45,6 @@ def _get_raw_data(name=None):
return data
-def _get_vrf_members(vrf: str) -> list:
- """
- Get list of interface VRF members
- :param vrf: str
- :return: list
- """
- output = cmd(f'ip --json --brief link show master {vrf}')
- answer = json.loads(output)
- interfaces = []
- for data in answer:
- if 'ifname' in data:
- interfaces.append(data.get('ifname'))
- return interfaces if len(interfaces) > 0 else ['n/a']
-
-
def _get_formatted_output(raw_data):
data_entries = []
for vrf in raw_data:
@@ -67,7 +52,9 @@ def _get_formatted_output(raw_data):
state = vrf.get('operstate').lower()
hw_address = vrf.get('address')
flags = ','.join(vrf.get('flags')).lower()
- members = ','.join(_get_vrf_members(name))
+ tmp = get_vrf_members(name)
+ if tmp: members = ','.join(get_vrf_members(name))
+ else: members = 'n/a'
data_entries.append([name, state, hw_address, flags, members])
headers = ["Name", "State", "MAC address", "Flags", "Interfaces"]
diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py
index cb843ff09..ba50d06cc 100644
--- a/src/tests/test_initial_setup.py
+++ b/src/tests/test_initial_setup.py
@@ -21,14 +21,16 @@ import vyos.configtree
import vyos.initialsetup as vis
from unittest import TestCase
-from vyos import xml
+from vyos.xml_ref import definition
+from vyos.xml_ref.pkg_cache.vyos_1x_cache import reference
class TestInitialSetup(TestCase):
def setUp(self):
with open('tests/data/config.boot.default', 'r') as f:
config_string = f.read()
self.config = vyos.configtree.ConfigTree(config_string)
- self.xml = xml.load_configuration()
+ self.xml = definition.Xml()
+ self.xml.define(reference)
def test_set_user_password(self):
vis.set_user_password(self.config, 'vyos', 'vyosvyos')