summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--data/configd-include.json5
-rw-r--r--data/templates/conntrack/nftables-ct.tmpl52
-rw-r--r--data/templates/dhcp-server/dhcpd.conf.tmpl2
-rw-r--r--data/templates/firewall/nftables-defines.tmpl32
-rw-r--r--data/templates/firewall/nftables-nat.tmpl4
-rw-r--r--data/templates/firewall/nftables-policy.tmpl55
-rw-r--r--data/templates/firewall/nftables.tmpl274
-rw-r--r--data/templates/firewall/upnpd.conf.tmpl172
-rw-r--r--data/templates/frr/ospfd.frr.tmpl3
-rw-r--r--data/templates/frr/static_routes_macro.j22
-rw-r--r--data/templates/high-availability/keepalived.conf.tmpl (renamed from data/templates/vrrp/keepalived.conf.tmpl)75
-rw-r--r--data/templates/https/nginx.default.tmpl6
-rw-r--r--data/templates/ipsec/swanctl/peer.tmpl3
-rw-r--r--data/templates/monitoring/override.conf.tmpl7
-rw-r--r--data/templates/monitoring/syslog_telegraf.tmpl5
-rw-r--r--data/templates/monitoring/systemd_vyos_telegraf_service.tmpl16
-rw-r--r--data/templates/monitoring/telegraf.tmpl60
-rw-r--r--data/templates/nhrp/opennhrp.conf.tmpl2
-rw-r--r--data/templates/ntp/ntpd.conf.tmpl1
-rw-r--r--data/templates/ssh/sshd_config.tmpl1
-rw-r--r--data/templates/zone_policy/nftables.tmpl113
-rw-r--r--debian/control6
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/firewall.xml.in297
-rw-r--r--interface-definitions/high-availability.xml.in (renamed from interface-definitions/vrrp.xml.in)181
-rw-r--r--interface-definitions/include/conntrack/log-common.xml.i20
-rw-r--r--interface-definitions/include/conntrack/timeout-common-protocols.xml.i172
-rw-r--r--interface-definitions/include/firewall/action.xml.i16
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i106
-rw-r--r--interface-definitions/include/firewall/icmp-type-name.xml.i142
-rw-r--r--interface-definitions/include/firewall/icmpv6-type-name.xml.i73
-rw-r--r--interface-definitions/include/firewall/mac-group.xml.i10
-rw-r--r--interface-definitions/include/firewall/port.xml.i5
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv6.xml.i34
-rw-r--r--interface-definitions/include/firewall/source-destination-group.xml.i10
-rw-r--r--interface-definitions/include/firewall/tcp-flags.xml.i119
-rw-r--r--interface-definitions/include/interface/inbound-interface.xml.i10
-rw-r--r--interface-definitions/include/interface/interface-firewall-vif-c.xml.i79
-rw-r--r--interface-definitions/include/interface/interface-firewall-vif.xml.i79
-rw-r--r--interface-definitions/include/interface/interface-firewall.xml.i79
-rw-r--r--interface-definitions/include/interface/interface-policy-vif-c.xml.i26
-rw-r--r--interface-definitions/include/interface/interface-policy-vif.xml.i26
-rw-r--r--interface-definitions/include/interface/interface-policy.xml.i26
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i4
-rw-r--r--interface-definitions/include/interface/vif.xml.i2
-rw-r--r--interface-definitions/include/listen-address.xml.i1
-rw-r--r--interface-definitions/include/nat-port.xml.i9
-rw-r--r--interface-definitions/include/nat-rule.xml.i8
-rw-r--r--interface-definitions/include/nat-translation-port.xml.i3
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i12
-rw-r--r--interface-definitions/include/policy/route-common-rule-ipv6.xml.i553
-rw-r--r--interface-definitions/include/policy/route-common-rule.xml.i402
-rw-r--r--interface-definitions/include/policy/route-rule-action.xml.i17
-rw-r--r--interface-definitions/include/ssh-user.xml.i4
-rw-r--r--interface-definitions/interfaces-bonding.xml.in2
-rw-r--r--interface-definitions/interfaces-bridge.xml.in4
-rw-r--r--interface-definitions/interfaces-dummy.xml.in2
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-geneve.xml.in2
-rw-r--r--interface-definitions/interfaces-l2tpv3.xml.in2
-rw-r--r--interface-definitions/interfaces-macsec.xml.in2
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in2
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in2
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in2
-rw-r--r--interface-definitions/interfaces-vti.xml.in2
-rw-r--r--interface-definitions/interfaces-vxlan.xml.in2
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in3
-rw-r--r--interface-definitions/interfaces-wireless.xml.in2
-rw-r--r--interface-definitions/interfaces-wwan.xml.in2
-rw-r--r--interface-definitions/policy-local-route.xml.in111
-rw-r--r--interface-definitions/policy-route.xml.in105
-rw-r--r--interface-definitions/policy.xml.in2
-rw-r--r--interface-definitions/service_monitoring_telegraf.xml.in113
-rw-r--r--interface-definitions/service_upnp.xml.in224
-rw-r--r--interface-definitions/system-conntrack.xml.in330
-rw-r--r--interface-definitions/vpn_ipsec.xml.in14
-rw-r--r--interface-definitions/zone-policy.xml.in146
-rw-r--r--op-mode-definitions/firewall.xml.in191
-rw-r--r--op-mode-definitions/policy-route.xml.in143
-rw-r--r--op-mode-definitions/show-virtual-server.xml.in13
-rw-r--r--op-mode-definitions/zone-policy.xml.in24
-rw-r--r--python/vyos/configdict.py6
-rw-r--r--python/vyos/configdiff.py30
-rw-r--r--python/vyos/configsource.py2
-rw-r--r--python/vyos/configtree.py65
-rw-r--r--python/vyos/firewall.py250
-rw-r--r--python/vyos/frr.py22
-rw-r--r--python/vyos/ifconfig/__init__.py1
-rwxr-xr-xpython/vyos/ifconfig/interface.py45
-rw-r--r--python/vyos/remote.py10
-rw-r--r--python/vyos/template.py63
-rw-r--r--python/vyos/util.py13
-rw-r--r--smoketest/configs/dialup-router-medium-vpn33
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py12
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py2
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py178
-rwxr-xr-xsmoketest/scripts/cli/test_ha_virtual_server.py146
-rwxr-xr-xsmoketest/scripts/cli/test_ha_vrrp.py40
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py16
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py329
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py134
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py9
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_telegraf.py65
-rwxr-xr-xsmoketest/scripts/cli/test_service_upnp.py105
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py24
-rwxr-xr-xsmoketest/scripts/cli/test_system_flow-accounting.py15
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py60
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py32
-rwxr-xr-xsmoketest/scripts/cli/test_zone_policy.py63
-rwxr-xr-xsrc/conf_mode/conntrack.py62
-rwxr-xr-xsrc/conf_mode/conntrack_sync.py2
-rwxr-xr-xsrc/conf_mode/containers.py28
-rwxr-xr-xsrc/conf_mode/firewall-interface.py175
-rwxr-xr-xsrc/conf_mode/firewall.py381
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py89
-rwxr-xr-xsrc/conf_mode/high-availability.py (renamed from src/conf_mode/vrrp.py)63
-rwxr-xr-xsrc/conf_mode/nat.py35
-rwxr-xr-xsrc/conf_mode/nat66.py22
-rwxr-xr-xsrc/conf_mode/policy-local-route.py205
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py120
-rwxr-xr-xsrc/conf_mode/policy-route.py257
-rwxr-xr-xsrc/conf_mode/policy.py6
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py27
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py175
-rwxr-xr-xsrc/conf_mode/service_upnp.py157
-rwxr-xr-xsrc/conf_mode/zone_policy.py201
-rw-r--r--src/etc/systemd/system/keepalived.service.d/override.conf13
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_firewall_input_filter.py73
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_interfaces_input_filter.py88
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/vyos_services_input_filter.py61
-rwxr-xr-xsrc/helpers/strip-private.py10
-rwxr-xr-xsrc/helpers/vyos_net_name21
-rwxr-xr-xsrc/migration-scripts/bgp/1-to-233
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/1-to-283
-rwxr-xr-xsrc/migration-scripts/firewall/6-to-7226
-rwxr-xr-xsrc/migration-scripts/policy/1-to-286
-rwxr-xr-xsrc/op_mode/firewall.py361
-rwxr-xr-xsrc/op_mode/monitor_bandwidth_test.sh3
-rwxr-xr-xsrc/op_mode/policy_route.py189
-rwxr-xr-xsrc/op_mode/show_virtual_server.py33
-rwxr-xr-xsrc/op_mode/vrrp.py13
-rwxr-xr-xsrc/op_mode/zone_policy.py81
-rwxr-xr-xsrc/system/keepalived-fifo.py3
-rw-r--r--src/systemd/keepalived.service13
-rw-r--r--src/systemd/miniupnpd.service13
-rw-r--r--src/tests/test_validate.py4
-rwxr-xr-xsrc/validators/ip-address7
-rwxr-xr-xsrc/validators/ip-cidr7
-rwxr-xr-xsrc/validators/ip-host7
-rwxr-xr-xsrc/validators/ip-prefix7
-rwxr-xr-xsrc/validators/ip-protocol3
-rwxr-xr-xsrc/validators/ipv47
-rwxr-xr-xsrc/validators/ipv4-address7
-rwxr-xr-xsrc/validators/ipv4-host7
-rwxr-xr-xsrc/validators/ipv4-multicast7
-rwxr-xr-xsrc/validators/ipv4-prefix7
-rwxr-xr-xsrc/validators/ipv4-range13
-rwxr-xr-xsrc/validators/ipv67
-rwxr-xr-xsrc/validators/ipv6-address7
-rwxr-xr-xsrc/validators/ipv6-host7
-rwxr-xr-xsrc/validators/ipv6-link-local12
-rwxr-xr-xsrc/validators/ipv6-multicast7
-rwxr-xr-xsrc/validators/ipv6-prefix7
-rwxr-xr-xsrc/validators/ipv6-range1
-rwxr-xr-xsrc/validators/mac-address-firewall27
-rwxr-xr-xsrc/validators/port-multi45
-rwxr-xr-xsrc/validators/port-range35
-rwxr-xr-xsrc/validators/tcp-flag17
-rw-r--r--test-requirements.txt1
171 files changed, 9276 insertions, 1034 deletions
diff --git a/Makefile b/Makefile
index b582ef84c..29744b323 100644
--- a/Makefile
+++ b/Makefile
@@ -29,9 +29,6 @@ interface_definitions: $(config_xml_obj)
# XXX: delete top level node.def's that now live in other packages
# IPSec VPN EAP-RADIUS does not support source-address
rm -rf $(TMPL_DIR)/vpn/ipsec/remote-access/radius/source-address
- # T3568: firewall is yet not migrated to XML and Python - this is only a dummy
- rm -rf $(TMPL_DIR)/firewall/node.def
- rm -rf $(TMPL_DIR)/nfirewall
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
find $(TMPL_DIR) -name node.def -type f -empty -exec false {} + || sh -c 'echo "There are empty node.def files! Check your interface definitions." && exit 1'
diff --git a/data/configd-include.json b/data/configd-include.json
index ee4cb0d42..c85ab0725 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -7,6 +7,7 @@
"dns_forwarding.py",
"dynamic_dns.py",
"flow_accounting_conf.py",
+"high-availability.py",
"host_name.py",
"https.py",
"igmp_proxy.py",
@@ -54,6 +55,7 @@
"service_mdns-repeater.py",
"service_pppoe-server.py",
"service_router-advert.py",
+"service_upnp.py",
"ssh.py",
"system-ip.py",
"system-ipv6.py",
@@ -69,6 +71,5 @@
"vpn_pptp.py",
"vpn_sstp.py",
"vrf.py",
-"vrf_vni.py",
-"vrrp.py"
+"vrf_vni.py"
]
diff --git a/data/templates/conntrack/nftables-ct.tmpl b/data/templates/conntrack/nftables-ct.tmpl
new file mode 100644
index 000000000..c0fe5297d
--- /dev/null
+++ b/data/templates/conntrack/nftables-ct.tmpl
@@ -0,0 +1,52 @@
+#!/usr/sbin/nft -f
+
+{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %}
+{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %}
+
+# we first flush all chains and render the content from scratch - this makes
+# any delta check obsolete
+flush chain raw {{ nft_ct_ignore_name }}
+flush chain raw {{ nft_ct_timeout_name }}
+
+table raw {
+ chain {{ nft_ct_ignore_name }} {
+{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %}
+{% for rule, rule_config in ignore.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }}
+{% set nft_command = '' %}
+{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %}
+{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %}
+{% endif %}
+{% if rule_config.protocol is defined and rule_config.protocol is not none %}
+{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %}
+{% endif %}
+{% if rule_config.destination is defined and rule_config.destination is not none %}
+{% if rule_config.destination.address is defined and rule_config.destination.address is not none %}
+{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %}
+{% endif %}
+{% if rule_config.destination.port is defined and rule_config.destination.port is not none %}
+{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %}
+{% endif %}
+{% endif %}
+{% if rule_config.source is defined and rule_config.source is not none %}
+{% if rule_config.source.address is defined and rule_config.source.address is not none %}
+{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %}
+{% endif %}
+{% if rule_config.source.port is defined and rule_config.source.port is not none %}
+{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %}
+{% endif %}
+{% endif %}
+ {{ nft_command }} counter notrack comment ignore-{{ rule }}
+{% endfor %}
+{% endif %}
+ return
+ }
+ chain {{ nft_ct_timeout_name }} {
+{% if timeout is defined and timeout.custom is defined and timeout.custom.rule is defined and timeout.custom.rule is not none %}
+{% for rule, rule_config in timeout.custom.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }}
+{% endfor %}
+{% endif %}
+ return
+ }
+}
diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl
index 233e2cc53..da2f28ced 100644
--- a/data/templates/dhcp-server/dhcpd.conf.tmpl
+++ b/data/templates/dhcp-server/dhcpd.conf.tmpl
@@ -165,7 +165,7 @@ shared-network {{ network | replace('_','-') }} {
option wpad-url "{{ subnet_config.wpad_url }}";
{% endif %}
{% if subnet_config.client_prefix_length is defined and subnet_config.client_prefix_length is not none %}
- option subnet-mask {{ subnet_config.client_prefix_length }};
+ option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }};
{% endif %}
{% if subnet_config.lease is defined and subnet_config.lease is not none %}
default-lease-time {{ subnet_config.lease }};
diff --git a/data/templates/firewall/nftables-defines.tmpl b/data/templates/firewall/nftables-defines.tmpl
new file mode 100644
index 000000000..d9eb7c199
--- /dev/null
+++ b/data/templates/firewall/nftables-defines.tmpl
@@ -0,0 +1,32 @@
+{% if group is defined %}
+{% if group.address_group is defined %}
+{% for group_name, group_conf in group.address_group.items() %}
+define A_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.ipv6_address_group is defined %}
+{% for group_name, group_conf in group.ipv6_address_group.items() %}
+define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.mac_group is defined %}
+{% for group_name, group_conf in group.mac_group.items() %}
+define M_{{ group_name }} = { {{ group_conf.mac_address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.network_group is defined %}
+{% for group_name, group_conf in group.network_group.items() %}
+define N_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.ipv6_network_group is defined %}
+{% for group_name, group_conf in group.ipv6_network_group.items() %}
+define N6_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.port_group is defined %}
+{% for group_name, group_conf in group.port_group.items() %}
+define P_{{ group_name }} = { {{ group_conf.port | join(",") }} }
+{% endfor %}
+{% endif %}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl
index 40ed1b916..9ea880697 100644
--- a/data/templates/firewall/nftables-nat.tmpl
+++ b/data/templates/firewall/nftables-nat.tmpl
@@ -157,8 +157,8 @@ delete chain ip raw NAT_CONNTRACK
add chain ip raw NAT_CONNTRACK
add rule ip raw NAT_CONNTRACK counter accept
{% set base_command = 'add rule ip raw' %}
-{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYATTA_CT_HELPER
-{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYATTA_CT_HELPER
+{{ base_command }} PREROUTING position {{ pre_ct_ignore }} counter jump VYOS_CT_HELPER
+{{ base_command }} OUTPUT position {{ out_ct_ignore }} counter jump VYOS_CT_HELPER
{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK
{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK
{% endif %}
diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl
new file mode 100644
index 000000000..905ffcd09
--- /dev/null
+++ b/data/templates/firewall/nftables-policy.tmpl
@@ -0,0 +1,55 @@
+#!/usr/sbin/nft -f
+
+{% if cleanup_commands is defined %}
+{% for command in cleanup_commands %}
+{{ command }}
+{% endfor %}
+{% endif %}
+
+include "/run/nftables_defines.conf"
+
+table ip mangle {
+{% if first_install is defined %}
+ chain VYOS_PBR_PREROUTING {
+ type filter hook prerouting priority -150; policy accept;
+ }
+ chain VYOS_PBR_POSTROUTING {
+ type filter hook postrouting priority -150; policy accept;
+ }
+{% endif %}
+{% if route is defined and route is not none -%}
+{% for route_text, conf in route.items() %}
+ chain VYOS_PBR_{{ route_text }} {
+{% if conf.rule is defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
+ {{ rule_conf | nft_rule(route_text, rule_id, 'ip') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(route_text) }}
+ }
+{% endfor %}
+{%- endif %}
+}
+
+table ip6 mangle {
+{% if first_install is defined %}
+ chain VYOS_PBR6_PREROUTING {
+ type filter hook prerouting priority -150; policy accept;
+ }
+ chain VYOS_PBR6_POSTROUTING {
+ type filter hook postrouting priority -150; policy accept;
+ }
+{% endif %}
+{% if route6 is defined and route6 is not none %}
+{% for route_text, conf in route6.items() %}
+ chain VYOS_PBR6_{{ route_text }} {
+{% if conf.rule is defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
+ {{ rule_conf | nft_rule(route_text, rule_id, 'ip6') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(route_text) }}
+ }
+{% endfor %}
+{% endif %}
+}
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl
new file mode 100644
index 000000000..0cc977cf9
--- /dev/null
+++ b/data/templates/firewall/nftables.tmpl
@@ -0,0 +1,274 @@
+#!/usr/sbin/nft -f
+
+{% if cleanup_commands is defined %}
+{% for command in cleanup_commands %}
+{{ command }}
+{% endfor %}
+{% endif %}
+
+include "/run/nftables_defines.conf"
+
+table ip filter {
+{% if first_install is defined %}
+ chain VYOS_FW_FORWARD {
+ type filter hook forward priority 0; policy accept;
+ jump VYOS_POST_FW
+ }
+ chain VYOS_FW_LOCAL {
+ type filter hook input priority 0; policy accept;
+ jump VYOS_POST_FW
+ }
+ chain VYOS_FW_OUTPUT {
+ type filter hook output priority 0; policy accept;
+ jump VYOS_POST_FW
+ }
+ chain VYOS_POST_FW {
+ return
+ }
+ chain VYOS_FRAG_MARK {
+ type filter hook prerouting priority -450; policy accept;
+ ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return
+ }
+{% endif %}
+{% if name is defined %}
+{% set ns = namespace(sets=[]) %}
+{% for name_text, conf in name.items() %}
+ chain NAME_{{ name_text }} {
+{% if conf.rule is defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
+ {{ rule_conf | nft_rule(name_text, rule_id) }}
+{% if rule_conf.recent is defined %}
+{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(name_text) }}
+ }
+{% endfor %}
+{% for set_name in ns.sets %}
+ set RECENT_{{ set_name }} {
+ type ipv4_addr
+ size 65535
+ flags dynamic
+ }
+{% endfor %}
+{% endif %}
+{% if state_policy is defined %}
+ chain VYOS_STATE_POLICY {
+{% if state_policy.established is defined %}
+ {{ state_policy.established | nft_state_policy('established') }}
+{% endif %}
+{% if state_policy.invalid is defined %}
+ {{ state_policy.invalid | nft_state_policy('invalid') }}
+{% endif %}
+{% if state_policy.related is defined %}
+ {{ state_policy.related | nft_state_policy('related') }}
+{% endif %}
+ return
+ }
+{% endif %}
+}
+
+table ip6 filter {
+{% if first_install is defined %}
+ chain VYOS_FW6_FORWARD {
+ type filter hook forward priority 0; policy accept;
+ jump VYOS_POST_FW6
+ }
+ chain VYOS_FW6_LOCAL {
+ type filter hook input priority 0; policy accept;
+ jump VYOS_POST_FW6
+ }
+ chain VYOS_FW6_OUTPUT {
+ type filter hook output priority 0; policy accept;
+ jump VYOS_POST_FW6
+ }
+ chain VYOS_POST_FW6 {
+ return
+ }
+ chain VYOS_FRAG6_MARK {
+ type filter hook prerouting priority -450; policy accept;
+ exthdr frag exists meta mark set 0xffff1 return
+ }
+{% endif %}
+{% if ipv6_name is defined %}
+{% set ns = namespace(sets=[]) %}
+{% for name_text, conf in ipv6_name.items() %}
+ chain NAME6_{{ name_text }} {
+{% if conf.rule is defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
+ {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }}
+{% if rule_conf.recent is defined %}
+{% set ns.sets = ns.sets + [name_text + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(name_text) }}
+ }
+{% endfor %}
+{% for set_name in ns.sets %}
+ set RECENT6_{{ set_name }} {
+ type ipv6_addr
+ size 65535
+ flags dynamic
+ }
+{% endfor %}
+{% endif %}
+{% if state_policy is defined %}
+ chain VYOS_STATE_POLICY6 {
+{% if state_policy.established is defined %}
+ {{ state_policy.established | nft_state_policy('established', ipv6=True) }}
+{% endif %}
+{% if state_policy.invalid is defined %}
+ {{ state_policy.invalid | nft_state_policy('invalid', ipv6=True) }}
+{% endif %}
+{% if state_policy.related is defined %}
+ {{ state_policy.related | nft_state_policy('related', ipv6=True) }}
+{% endif %}
+ return
+ }
+{% endif %}
+}
+
+{% if first_install is defined %}
+table ip nat {
+ chain PREROUTING {
+ type nat hook prerouting priority -100; policy accept;
+ counter jump VYOS_PRE_DNAT_HOOK
+ }
+
+ chain POSTROUTING {
+ type nat hook postrouting priority 100; policy accept;
+ counter jump VYOS_PRE_SNAT_HOOK
+ }
+
+ chain VYOS_PRE_DNAT_HOOK {
+ return
+ }
+
+ chain VYOS_PRE_SNAT_HOOK {
+ return
+ }
+}
+
+table ip6 nat {
+ chain PREROUTING {
+ type nat hook prerouting priority -100; policy accept;
+ counter jump VYOS_DNPT_HOOK
+ }
+
+ chain POSTROUTING {
+ type nat hook postrouting priority 100; policy accept;
+ counter jump VYOS_SNPT_HOOK
+ }
+
+ chain VYOS_DNPT_HOOK {
+ return
+ }
+
+ chain VYOS_SNPT_HOOK {
+ return
+ }
+}
+
+table inet mangle {
+ chain FORWARD {
+ type filter hook forward priority -150; policy accept;
+ }
+}
+
+table raw {
+ chain VYOS_TCP_MSS {
+ type filter hook forward priority -300; policy accept;
+ }
+
+ chain PREROUTING {
+ type filter hook prerouting priority -200; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
+ counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain OUTPUT {
+ type filter hook output priority -200; policy accept;
+ counter jump VYOS_CT_IGNORE
+ counter jump VYOS_CT_TIMEOUT
+ counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ ct helper rpc_tcp {
+ type "rpc" protocol tcp;
+ }
+
+ ct helper rpc_udp {
+ type "rpc" protocol udp;
+ }
+
+ ct helper tns_tcp {
+ type "tns" protocol tcp;
+ }
+
+ chain VYOS_CT_HELPER {
+ ct helper set "rpc_tcp" tcp dport {111} return
+ ct helper set "rpc_udp" udp dport {111} return
+ ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
+ return
+ }
+
+ chain VYOS_CT_IGNORE {
+ return
+ }
+
+ chain VYOS_CT_TIMEOUT {
+ return
+ }
+
+ chain VYOS_CT_PREROUTING_HOOK {
+ return
+ }
+
+ chain VYOS_CT_OUTPUT_HOOK {
+ return
+ }
+
+ chain FW_CONNTRACK {
+ accept
+ }
+}
+
+table ip6 raw {
+ chain VYOS_TCP_MSS {
+ type filter hook forward priority -300; policy accept;
+ }
+
+ chain PREROUTING {
+ type filter hook prerouting priority -300; policy accept;
+ counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain OUTPUT {
+ type filter hook output priority -300; policy accept;
+ counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
+ notrack
+ }
+
+ chain VYOS_CT_PREROUTING_HOOK {
+ return
+ }
+
+ chain VYOS_CT_OUTPUT_HOOK {
+ return
+ }
+
+ chain FW_CONNTRACK {
+ accept
+ }
+}
+{% endif %}
diff --git a/data/templates/firewall/upnpd.conf.tmpl b/data/templates/firewall/upnpd.conf.tmpl
new file mode 100644
index 000000000..39cb21373
--- /dev/null
+++ b/data/templates/firewall/upnpd.conf.tmpl
@@ -0,0 +1,172 @@
+# This is the UPNP configuration file
+
+# WAN network interface
+ext_ifname={{ wan_interface }}
+{% if wan_ip is defined %}
+# If the WAN interface has several IP addresses, you
+# can specify the one to use below
+{% for addr in wan_ip %}
+ext_ip={{ addr }}
+{% endfor %}
+{% endif %}
+
+# LAN network interfaces IPs / networks
+{% if listen is defined %}
+# There can be multiple listening IPs for SSDP traffic, in that case
+# use multiple 'listening_ip=...' lines, one for each network interface.
+# It can be IP address or network interface name (ie. "eth0")
+# It is mandatory to use the network interface name in order to enable IPv6
+# HTTP is available on all interfaces.
+# When MULTIPLE_EXTERNAL_IP is enabled, the external IP
+# address associated with the subnet follows. For example:
+# listening_ip=192.168.0.1/24 88.22.44.13
+{% for addr in listen %}
+{% if addr | is_ipv4 %}
+listening_ip={{ addr }}
+{% elif addr | is_ipv6 %}
+ipv6_listening_ip={{ addr }}
+{% else %}
+listening_ip={{ addr }}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+# CAUTION: mixing up WAN and LAN interfaces may introduce security risks!
+# Be sure to assign the correct interfaces to LAN and WAN and consider
+# implementing UPnP permission rules at the bottom of this configuration file
+
+# Port for HTTP (descriptions and SOAP) traffic. Set to 0 for autoselect.
+#http_port=0
+# Port for HTTPS. Set to 0 for autoselect (default)
+#https_port=0
+
+# Path to the UNIX socket used to communicate with MiniSSDPd
+# If running, MiniSSDPd will manage M-SEARCH answering.
+# default is /var/run/minissdpd.sock
+#minissdpdsocket=/var/run/minissdpd.sock
+
+{% if nat_pmp is defined %}
+# Enable NAT-PMP support (default is no)
+enable_natpmp=yes
+{% endif %}
+
+# Enable UPNP support (default is yes)
+enable_upnp=yes
+
+{% if pcp_lifetime is defined %}
+# PCP
+# Configure the minimum and maximum lifetime of a port mapping in seconds
+# 120s and 86400s (24h) are suggested values from PCP-base
+{% if pcp_lifetime.max is defined %}
+max_lifetime={{ pcp_lifetime.max }}
+{% endif %}
+{% if pcp_lifetime.min is defined %}
+min_lifetime={{ pcp_lifetime.min }}
+{% endif %}
+{% endif %}
+
+
+# To enable the next few runtime options, see compile time
+# ENABLE_MANUFACTURER_INFO_CONFIGURATION (config.h)
+
+{% if friendly_name is defined %}
+# Name of this service, default is "`uname -s` router"
+friendly_name= {{ friendly_name }}
+{% endif %}
+
+# Manufacturer name, default is "`uname -s`"
+manufacturer_name=VyOS
+
+# Manufacturer URL, default is URL of OS vendor
+manufacturer_url=https://vyos.io/
+
+# Model name, default is "`uname -s` router"
+model_name=VyOS Router Model
+
+# Model description, default is "`uname -s` router"
+model_description=Vyos open source enterprise router/firewall operating system
+
+# Model URL, default is URL of OS vendor
+model_url=https://vyos.io/
+
+{% if secure_mode is defined %}
+# Secure Mode, UPnP clients can only add mappings to their own IP
+secure_mode=yes
+{% else %}
+# Secure Mode, UPnP clients can only add mappings to their own IP
+secure_mode=no
+{% endif %}
+
+{% if presentation_url is defined %}
+# Default presentation URL is HTTP address on port 80
+# If set to an empty string, no presentationURL element will appear
+# in the XML description of the device, which prevents MS Windows
+# from displaying an icon in the "Network Connections" panel.
+#presentation_url= {{ presentation_url }}
+{% endif %}
+
+# Report system uptime instead of daemon uptime
+system_uptime=yes
+
+# Unused rules cleaning.
+# never remove any rule before this threshold for the number
+# of redirections is exceeded. default to 20
+clean_ruleset_threshold=10
+# Clean process work interval in seconds. default to 0 (disabled).
+# a 600 seconds (10 minutes) interval makes sense
+clean_ruleset_interval=600
+
+# Anchor name in pf (default is miniupnpd)
+anchor=VyOS
+
+uuid={{ uuid }}
+
+# Lease file location
+lease_file=/config/upnp.leases
+
+# Daemon's serial and model number when reporting to clients
+# (in XML description)
+#serial=12345678
+#model_number=1
+
+{% if rules is defined %}
+# UPnP permission rules
+# (allow|deny) (external port range) IP/mask (internal port range)
+# A port range is <min port>-<max port> or <port> if there is only
+# one port in the range.
+# IP/mask format must be nnn.nnn.nnn.nnn/nn
+# It is advised to only allow redirection of port >= 1024
+# and end the rule set with "deny 0-65535 0.0.0.0/0 0-65535"
+# The following default ruleset allows specific LAN side IP addresses
+# to request only ephemeral ports. It is recommended that users
+# modify the IP ranges to match their own internal networks, and
+# also consider implementing network-specific restrictions
+# CAUTION: failure to enforce any rules may permit insecure requests to be made!
+{% for rule, config in rules.items() %}
+{% if config.disable is defined %}
+{{ config.action}} {{ config.external_port_range }} {{ config.ip }} {{ config.internal_port_range }}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{% if stun is defined %}
+# WAN interface must have public IP address. Otherwise it is behind NAT
+# and port forwarding is impossible. In some cases WAN interface can be
+# behind unrestricted NAT 1:1 when all incoming traffic is NAT-ed and
+# routed to WAN interfaces without any filtering. In this cases miniupnpd
+# needs to know public IP address and it can be learnt by asking external
+# server via STUN protocol. Following option enable retrieving external
+# public IP address from STUN server and detection of NAT type. You need
+# to specify also external STUN server in stun_host option below.
+# This option is disabled by default.
+ext_perform_stun=yes
+# Specify STUN server, either hostname or IP address
+# Some public STUN servers:
+# stun.stunprotocol.org
+# stun.sipgate.net
+# stun.xten.com
+# stun.l.google.com (on non standard port 19302)
+ext_stun_host={{ stun.host }}
+# Specify STUN UDP port, by default it is standard port 3478.
+ext_stun_port={{ stun.port }}
+{% endif %}
diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl
index af66baf53..a6618b6af 100644
--- a/data/templates/frr/ospfd.frr.tmpl
+++ b/data/templates/frr/ospfd.frr.tmpl
@@ -126,6 +126,9 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }}
{% if default_metric is defined and default_metric is not none %}
default-metric {{ default_metric }}
{% endif %}
+{% if maximum_paths is defined and maximum_paths is not none %}
+ maximum-paths {{ maximum_paths }}
+{% endif %}
{% if distance is defined and distance is not none %}
{% if distance.global is defined and distance.global is not none %}
distance {{ distance.global }}
diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2
index 3b432b49b..86c7470ca 100644
--- a/data/templates/frr/static_routes_macro.j2
+++ b/data/templates/frr/static_routes_macro.j2
@@ -5,7 +5,7 @@
{% if prefix_config.dhcp_interface is defined and prefix_config.dhcp_interface is not none %}
{% set next_hop = prefix_config.dhcp_interface | get_dhcp_router %}
{% if next_hop is defined and next_hop is not none %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }}
+{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ prefix_config.dhcp_interface }} {{ 'table ' + table if table is defined and table is not none }}
{% endif %}
{% endif %}
{% if prefix_config.interface is defined and prefix_config.interface is not none %}
diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/high-availability/keepalived.conf.tmpl
index 6585fc60b..68c707f17 100644
--- a/data/templates/vrrp/keepalived.conf.tmpl
+++ b/data/templates/high-availability/keepalived.conf.tmpl
@@ -9,8 +9,8 @@ global_defs {
notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py
}
-{% if group is defined and group is not none %}
-{% for name, group_config in group.items() if group_config.disable is not defined %}
+{% if vrrp is defined and vrrp.group is defined and vrrp.group is not none %}
+{% for name, group_config in vrrp.group.items() if group_config.disable is not defined %}
{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %}
vrrp_script healthcheck_{{ name }} {
script "{{ group_config.health_check.script }}"
@@ -28,6 +28,9 @@ vrrp_instance {{ name }} {
virtual_router_id {{ group_config.vrid }}
priority {{ group_config.priority }}
advert_int {{ group_config.advertise_interval }}
+{% if group_config.track is defined and group_config.track.exclude_vrrp_interface is defined %}
+ dont_track_primary
+{% endif %}
{% if group_config.no_preempt is not defined and group_config.preempt_delay is defined and group_config.preempt_delay is not none %}
preempt_delay {{ group_config.preempt_delay }}
{% elif group_config.no_preempt is defined %}
@@ -61,8 +64,8 @@ vrrp_instance {{ name }} {
{% endif %}
{% if group_config.address is defined and group_config.address is not none %}
virtual_ipaddress {
-{% for addr in group_config.address %}
- {{ addr }}
+{% for addr, addr_config in group_config.address.items() %}
+ {{ addr }}{{ ' dev ' + addr_config.interface if addr_config.interface is defined }}
{% endfor %}
}
{% endif %}
@@ -73,6 +76,13 @@ vrrp_instance {{ name }} {
{% endfor %}
}
{% endif %}
+{% if group_config.track is defined and group_config.track.interface is defined and group_config.track.interface is not none %}
+ track_interface {
+{% for interface in group_config.track.interface %}
+ {{ interface }}
+{% endfor %}
+ }
+{% endif %}
{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %}
track_script {
healthcheck_{{ name }}
@@ -82,8 +92,8 @@ vrrp_instance {{ name }} {
{% endfor %}
{% endif %}
-{% if sync_group is defined and sync_group is not none %}
-{% for name, sync_group_config in sync_group.items() if sync_group_config.disable is not defined %}
+{% if vrrp is defined and vrrp.sync_group is defined and vrrp.sync_group is not none %}
+{% for name, sync_group_config in vrrp.sync_group.items() if sync_group_config.disable is not defined %}
vrrp_sync_group {{ name }} {
group {
{% if sync_group_config.member is defined and sync_group_config.member is not none %}
@@ -94,13 +104,15 @@ vrrp_sync_group {{ name }} {
}
{# Health-check scripts should be in section sync-group if member is part of the sync-group T4081 #}
-{% for name, group_config in group.items() if group_config.disable is not defined %}
-{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none and name in sync_group_config.member %}
+{% if vrrp is defined and vrrp.group is defined and vrrp.group is not none %}
+{% for name, group_config in vrrp.group.items() if group_config.disable is not defined %}
+{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none and name in sync_group_config.member %}
track_script {
healthcheck_{{ name }}
}
+{% endif %}
+{% endfor %}
{% endif %}
-{% endfor %}
{% if conntrack_sync_group is defined and conntrack_sync_group == name %}
{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %}
notify_master "{{ vyos_helper }} master {{ name }}"
@@ -110,3 +122,48 @@ vrrp_sync_group {{ name }} {
}
{% endfor %}
{% endif %}
+
+{% if virtual_server is defined and virtual_server is not none %}
+# Virtual-server configuration
+{% for vserver, vserver_config in virtual_server.items() %}
+virtual_server {{ vserver }} {{ vserver_config.port }} {
+ delay_loop {{ vserver_config.delay_loop }}
+{% if vserver_config.algorithm == 'round-robin' %}
+ lb_algo rr
+{% elif vserver_config.algorithm == 'weighted-round-robin' %}
+ lb_algo wrr
+{% elif vserver_config.algorithm == 'least-connection' %}
+ lb_algo lc
+{% elif vserver_config.algorithm == 'weighted-least-connection' %}
+ lb_algo wlc
+{% elif vserver_config.algorithm == 'source-hashing' %}
+ lb_algo sh
+{% elif vserver_config.algorithm == 'destination-hashing' %}
+ lb_algo dh
+{% elif vserver_config.algorithm == 'locality-based-least-connection' %}
+ lb_algo lblc
+{% endif %}
+{% if vserver_config.forward_method == "nat" %}
+ lb_kind NAT
+{% elif vserver_config.forward_method == "direct" %}
+ lb_kind DR
+{% elif vserver_config.forward_method == "tunnel" %}
+ lb_kind TUN
+{% endif %}
+ persistence_timeout {{ vserver_config.persistence_timeout }}
+ protocol {{ vserver_config.protocol | upper }}
+{% if vserver_config.real_server is defined and vserver_config.real_server is not none %}
+{% for rserver, rserver_config in vserver_config.real_server.items() %}
+ real_server {{ rserver }} {{ rserver_config.port }} {
+ weight 1
+ {{ vserver_config.protocol | upper }}_CHECK {
+{% if rserver_config.connection_timeout is defined and rserver_config.connection_timeout is not none %}
+ connect_timeout {{ rserver_config.connection_timeout }}
+{% endif %}
+ }
+ }
+{% endfor %}
+{% endif %}
+}
+{% endfor %}
+{% endif %}
diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl
index ac9203e83..e8511bd62 100644
--- a/data/templates/https/nginx.default.tmpl
+++ b/data/templates/https/nginx.default.tmpl
@@ -1,12 +1,6 @@
### Autogenerated by https.py ###
# Default server configuration
#
-server {
- listen 80 default_server;
- listen [::]:80 default_server;
- server_name _;
- return 301 https://$host$request_uri;
-}
{% for server in server_block_list %}
server {
diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl
index 1b221814e..c6b71f2a1 100644
--- a/data/templates/ipsec/swanctl/peer.tmpl
+++ b/data/templates/ipsec/swanctl/peer.tmpl
@@ -101,6 +101,9 @@
{% set remote_prefix = tunnel_conf.remote.prefix if 'any' not in tunnel_conf.remote.prefix else ['0.0.0.0/0', '::/0'] %}
remote_ts = {{ remote_prefix | join(remote_suffix + ",") }}{{ remote_suffix }}
{% endif %}
+{% if tunnel_conf.priority is defined and tunnel_conf.priority is not none %}
+ priority = {{ tunnel_conf.priority }}
+{% endif %}
{% elif tunnel_esp.mode == 'transport' %}
local_ts = {{ peer_conf.local_address }}{{ local_suffix }}
remote_ts = {{ peer }}{{ remote_suffix }}
diff --git a/data/templates/monitoring/override.conf.tmpl b/data/templates/monitoring/override.conf.tmpl
new file mode 100644
index 000000000..f8f150791
--- /dev/null
+++ b/data/templates/monitoring/override.conf.tmpl
@@ -0,0 +1,7 @@
+[Unit]
+After=vyos-router.service
+ConditionPathExists=/run/telegraf/vyos-telegraf.conf
+[Service]
+Environment=INFLUX_TOKEN={{ authentication.token }}
+CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN
+AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
diff --git a/data/templates/monitoring/syslog_telegraf.tmpl b/data/templates/monitoring/syslog_telegraf.tmpl
new file mode 100644
index 000000000..cdcbd92a4
--- /dev/null
+++ b/data/templates/monitoring/syslog_telegraf.tmpl
@@ -0,0 +1,5 @@
+# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py
+
+$ModLoad omuxsock
+$OMUxSockSocket /run/telegraf/telegraf_syslog.sock
+*.notice :omuxsock:
diff --git a/data/templates/monitoring/systemd_vyos_telegraf_service.tmpl b/data/templates/monitoring/systemd_vyos_telegraf_service.tmpl
new file mode 100644
index 000000000..234ef5586
--- /dev/null
+++ b/data/templates/monitoring/systemd_vyos_telegraf_service.tmpl
@@ -0,0 +1,16 @@
+[Unit]
+Description=The plugin-driven server agent for reporting metrics into InfluxDB
+Documentation=https://github.com/influxdata/telegraf
+After=network.target
+
+[Service]
+EnvironmentFile=-/etc/default/telegraf
+User=telegraf
+ExecStart=/usr/bin/telegraf -config /run/telegraf/vyos-telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS
+ExecReload=/bin/kill -HUP $MAINPID
+Restart=on-failure
+RestartForceExitStatus=SIGPIPE
+KillMode=control-group
+
+[Install]
+WantedBy=multi-user.target
diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl
new file mode 100644
index 000000000..d3145a500
--- /dev/null
+++ b/data/templates/monitoring/telegraf.tmpl
@@ -0,0 +1,60 @@
+# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py
+
+[agent]
+ interval = "10s"
+ round_interval = true
+ metric_batch_size = 1000
+ metric_buffer_limit = 10000
+ collection_jitter = "0s"
+ flush_interval = "10s"
+ flush_jitter = "0s"
+ precision = ""
+ debug = false
+ quiet = false
+ logfile = ""
+ hostname = ""
+ omit_hostname = false
+[[outputs.influxdb_v2]]
+ urls = ["{{ url }}:{{ port }}"]
+ insecure_skip_verify = true
+ token = "$INFLUX_TOKEN"
+ organization = "{{ authentication.organization }}"
+ bucket = "{{ bucket }}"
+[[inputs.cpu]]
+ percpu = true
+ totalcpu = true
+ collect_cpu_time = false
+ report_active = false
+[[inputs.disk]]
+ ignore_fs = ["devtmpfs", "devfs"]
+[[inputs.diskio]]
+[[inputs.mem]]
+[[inputs.net]]
+[[inputs.system]]
+[[inputs.netstat]]
+[[inputs.processes]]
+[[inputs.kernel]]
+[[inputs.interrupts]]
+[[inputs.linux_sysctl_fs]]
+[[inputs.systemd_units]]
+[[inputs.conntrack]]
+ files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"]
+ dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
+[[inputs.ethtool]]
+ interface_include = {{ interfaces_ethernet }}
+[[inputs.ntpq]]
+ dns_lookup = true
+[[inputs.internal]]
+[[inputs.nstat]]
+[[inputs.syslog]]
+ server = "unixgram:///run/telegraf/telegraf_syslog.sock"
+ best_effort = true
+ syslog_standard = "RFC3164"
+[[inputs.exec]]
+ commands = [
+ "{{ custom_scripts_dir }}/show_firewall_input_filter.py",
+ "{{ custom_scripts_dir }}/show_interfaces_input_filter.py",
+ "{{ custom_scripts_dir }}/vyos_services_input_filter.py"
+ ]
+ timeout = "10s"
+ data_format = "influx"
diff --git a/data/templates/nhrp/opennhrp.conf.tmpl b/data/templates/nhrp/opennhrp.conf.tmpl
index 948327198..e9e9f692a 100644
--- a/data/templates/nhrp/opennhrp.conf.tmpl
+++ b/data/templates/nhrp/opennhrp.conf.tmpl
@@ -33,7 +33,7 @@ interface {{ name }} #{{ type }} {{ profile_name }}
{% endfor %}
{% if tunnel_conf.shortcut_target is defined and tunnel_conf.shortcut_target is not none %}
{% for target, shortcut_conf in tunnel_conf.shortcut_target.items() %}
- shortcut-target {{ target }} {{ shortcut_conf.holding_time if shortcut_conf.holding_time is defined else '' }}
+ shortcut-target {{ target }}{{ ' holding-time ' + shortcut_conf.holding_time if shortcut_conf.holding_time is defined }}
{% endfor %}
{% endif %}
diff --git a/data/templates/ntp/ntpd.conf.tmpl b/data/templates/ntp/ntpd.conf.tmpl
index 38e68f24f..e7afcc16b 100644
--- a/data/templates/ntp/ntpd.conf.tmpl
+++ b/data/templates/ntp/ntpd.conf.tmpl
@@ -27,6 +27,7 @@ restrict -6 ::1
{% if allow_clients is defined and allow_clients.address is defined %}
# Allowed clients configuration
+restrict default ignore
{% for address in allow_clients.address %}
restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer
{% endfor %}
diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl
index 2f2b78a66..670cf85a1 100644
--- a/data/templates/ssh/sshd_config.tmpl
+++ b/data/templates/ssh/sshd_config.tmpl
@@ -29,6 +29,7 @@ UsePAM yes
PermitRootLogin no
PidFile /run/sshd/sshd.pid
AddressFamily any
+DebianBanner no
#
# User configurable section
diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl
new file mode 100644
index 000000000..093da6bd8
--- /dev/null
+++ b/data/templates/zone_policy/nftables.tmpl
@@ -0,0 +1,113 @@
+#!/usr/sbin/nft -f
+
+{% if cleanup_commands is defined %}
+{% for command in cleanup_commands %}
+{{ command }}
+{% endfor %}
+{% endif %}
+
+{% if zone is defined %}
+table ip filter {
+{% for zone_name, zone_conf in zone.items() if zone_conf.ipv4 %}
+{% if zone_conf.local_zone is defined %}
+ chain VZONE_{{ zone_name }}_IN {
+ iifname lo counter return
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+ chain VZONE_{{ zone_name }}_OUT {
+ oifname lo counter return
+{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.name is defined %}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+{% else %}
+ chain VZONE_{{ zone_name }} {
+ iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=False) }}
+{% if zone_conf.intra_zone_filtering is defined %}
+ iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% endif %}
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %}
+{% if zone[from_zone].local_zone is not defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endif %}
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+{% endif %}
+{% endfor %}
+}
+
+table ip6 filter {
+{% for zone_name, zone_conf in zone.items() if zone_conf.ipv6 %}
+{% if zone_conf.local_zone is defined %}
+ chain VZONE6_{{ zone_name }}_IN {
+ iifname lo counter return
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+ chain VZONE6_{{ zone_name }}_OUT {
+ oifname lo counter return
+{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is defined %}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
+ oifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+{% else %}
+ chain VZONE6_{{ zone_name }} {
+ iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }}
+{% if zone_conf.intra_zone_filtering is defined %}
+ iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% endif %}
+{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %}
+{% if zone[from_zone].local_zone is not defined %}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }}
+ iifname { {{ zone[from_zone].interface | join(",") }} } counter return
+{% endif %}
+{% endfor %}
+ counter {{ zone_conf.default_action if zone_conf.default_action is defined else 'drop' }}
+ }
+{% endif %}
+{% endfor %}
+}
+
+{% for zone_name, zone_conf in zone.items() %}
+{% if zone_conf.ipv4 %}
+{% if 'local_zone' in zone_conf %}
+insert rule ip filter VYOS_FW_LOCAL counter jump VZONE_{{ zone_name }}_IN
+insert rule ip filter VYOS_FW_OUTPUT counter jump VZONE_{{ zone_name }}_OUT
+{% else %}
+insert rule ip filter VYOS_FW_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
+{% endif %}
+{% endif %}
+{% if zone_conf.ipv6 %}
+{% if 'local_zone' in zone_conf %}
+insert rule ip6 filter VYOS_FW6_LOCAL counter jump VZONE6_{{ zone_name }}_IN
+insert rule ip6 filter VYOS_FW6_OUTPUT counter jump VZONE6_{{ zone_name }}_OUT
+{% else %}
+insert rule ip6 filter VYOS_FW6_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }}
+{% endif %}
+{% endif %}
+{% endfor %}
+
+{# Ensure that state-policy rule is first in the chain #}
+{% if firewall.state_policy is defined %}
+{% for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] %}
+insert rule ip filter {{ chain }} jump VYOS_STATE_POLICY
+{% endfor %}
+{% for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] %}
+insert rule ip6 filter {{ chain }} jump VYOS_STATE_POLICY6
+{% endfor %}
+{% endif %}
+
+{% endif %}
diff --git a/debian/control b/debian/control
index ef124679b..c53e4d3b8 100644
--- a/debian/control
+++ b/debian/control
@@ -94,6 +94,7 @@ Depends:
ndisc6,
ndppd,
netplug,
+ nfct,
nftables (>= 0.9.3),
nginx-light,
ntp,
@@ -151,6 +152,7 @@ Depends:
strongswan-swanctl (>= 5.9),
sudo,
systemd,
+ telegraf (>= 1.20),
tcpdump,
tcptraceroute,
telnet,
@@ -168,7 +170,9 @@ Depends:
wide-dhcpv6-client,
wireguard-tools,
wireless-regdb,
- wpasupplicant (>= 0.6.7)
+ wpasupplicant (>= 0.6.7),
+ ndppd,
+ miniupnpd-nftables
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 29d74390f..63dff43a5 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -12,6 +12,7 @@ etc/security
etc/sudoers.d
etc/systemd
etc/sysctl.d
+etc/telegraf
etc/udev
etc/update-motd.d
etc/vyos
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 1f3c14208..f2aca4b3a 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="nfirewall" owner="${vyos_conf_scripts_dir}/firewall.py">
+ <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py">
<properties>
<priority>199</priority>
<help>Firewall</help>
@@ -24,6 +24,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<leafNode name="broadcast-ping">
<properties>
@@ -43,6 +44,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="config-trap">
<properties>
@@ -62,6 +64,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<node name="group">
<properties>
@@ -71,6 +74,9 @@
<tagNode name="address-group">
<properties>
<help>Firewall address-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
</properties>
<children>
<leafNode name="address">
@@ -97,6 +103,9 @@
<tagNode name="ipv6-address-group">
<properties>
<help>Firewall ipv6-address-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
</properties>
<children>
<leafNode name="address">
@@ -106,8 +115,13 @@
<format>ipv6</format>
<description>IPv6 address to match</description>
</valueHelp>
+ <valueHelp>
+ <format>ipv6range</format>
+ <description>IPv6 range to match (e.g. 2002::1-2002::ff)</description>
+ </valueHelp>
<constraint>
<validator name="ipv6-address"/>
+ <validator name="ipv6-range"/>
</constraint>
<multi/>
</properties>
@@ -117,7 +131,10 @@
</tagNode>
<tagNode name="ipv6-network-group">
<properties>
- <help>Network-group member</help>
+ <help>Firewall ipv6-network-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
</properties>
<children>
#include <include/generic-description.xml.i>
@@ -136,9 +153,36 @@
</leafNode>
</children>
</tagNode>
+ <tagNode name="mac-group">
+ <properties>
+ <help>Firewall mac-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <leafNode name="mac-address">
+ <properties>
+ <help>Mac-group member</help>
+ <valueHelp>
+ <format>&lt;MAC address&gt;</format>
+ <description>MAC address to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
<tagNode name="network-group">
<properties>
<help>Firewall network-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
</properties>
<children>
#include <include/generic-description.xml.i>
@@ -160,6 +204,9 @@
<tagNode name="port-group">
<properties>
<help>Firewall port-group</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
</properties>
<children>
#include <include/generic-description.xml.i>
@@ -179,6 +226,9 @@
<description>Numbered port range (e.g. 1001-1050)</description>
</valueHelp>
<multi/>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
</properties>
</leafNode>
</children>
@@ -203,10 +253,14 @@
<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/name-default-action.xml.i>
@@ -233,7 +287,7 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
</children>
</node>
@@ -243,7 +297,7 @@
</properties>
<children>
#include <include/firewall/address-ipv6.xml.i>
- #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
#include <include/firewall/port.xml.i>
</children>
</node>
@@ -296,227 +350,31 @@
<help>ICMPv6 type and code information</help>
</properties>
<children>
- <leafNode name="type">
+ <leafNode name="code">
<properties>
- <help>ICMP type-name</help>
- <completionHelp>
- <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply</list>
- </completionHelp>
- <valueHelp>
- <format>any</format>
- <description>Any ICMP type/code</description>
- </valueHelp>
- <valueHelp>
- <format>echo-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>pong</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>destination-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>protocol-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>port-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>fragmentation-needed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>source-route-failed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>communication-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-precedence-violation</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>precedence-cutoff</format>
- <description>ICMP type/code name</description>
- </valueHelp>
+ <help>ICMPv6 code (0-255)</help>
<valueHelp>
- <format>source-quench</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS host-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>echo-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ping</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>router-advertisement</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>router-solicitation</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>time-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-transit</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-reassembly</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>parameter-problem</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ip-header-bad</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>required-option-missing</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>timestamp-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>timestamp-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>address-mask-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>address-mask-reply</format>
- <description>ICMP type/code name</description>
+ <format>u32:0-255</format>
+ <description>ICMPv6 code (0-255)</description>
</valueHelp>
<constraint>
- <regex>^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply)$</regex>
<validator name="numeric" argument="--range 0-255"/>
</constraint>
</properties>
</leafNode>
- </children>
- </node>
- <node name="p2p">
- <properties>
- <help>P2P application packets</help>
- </properties>
- <children>
- <leafNode name="all">
- <properties>
- <help>AppleJuice/BitTorrent/Direct Connect/eDonkey/eMule/Gnutella/KaZaA application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="applejuice">
- <properties>
- <help>AppleJuice application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="bittorrent">
- <properties>
- <help>BitTorrent application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="directconnect">
- <properties>
- <help>Direct Connect application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="edonkey">
- <properties>
- <help>eDonkey/eMule application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="gnutella">
- <properties>
- <help>Gnutella application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="kazaa">
+ <leafNode name="type">
<properties>
- <help>KaZaA application packets</help>
- <valueless/>
+ <help>ICMPv6 type (0-255)</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>
</children>
@@ -541,6 +399,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="ipv6-src-route">
<properties>
@@ -560,6 +419,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="log-martians">
<properties>
@@ -579,10 +439,14 @@
<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/name-default-action.xml.i>
@@ -678,6 +542,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="send-redirects">
<properties>
@@ -697,6 +562,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<leafNode name="source-validation">
<properties>
@@ -720,6 +586,7 @@
<regex>^(strict|loose|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<node name="state-policy">
<properties>
@@ -773,6 +640,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<leafNode name="twa-hazards-protection">
<properties>
@@ -792,6 +660,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
</children>
</node>
diff --git a/interface-definitions/vrrp.xml.in b/interface-definitions/high-availability.xml.in
index 53d79caac..ee1d70484 100644
--- a/interface-definitions/vrrp.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -1,13 +1,13 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="high-availability">
+ <node name="high-availability" owner="${vyos_conf_scripts_dir}/high-availability.py">
<properties>
+ <priority>800</priority> <!-- after all interfaces and conntrack-sync -->
<help>High availability settings</help>
</properties>
<children>
- <node name="vrrp" owner="${vyos_conf_scripts_dir}/vrrp.py">
+ <node name="vrrp">
<properties>
- <priority>800</priority> <!-- after all interfaces and conntrack-sync -->
<help>Virtual Router Redundancy Protocol settings</help>
</properties>
<children>
@@ -177,8 +177,37 @@
<valueless/>
</properties>
</leafNode>
+ <node name="track">
+ <properties>
+ <help>Track settings</help>
+ </properties>
+ <children>
+ <leafNode name="exclude-vrrp-interface">
+ <properties>
+ <valueless/>
+ <help>Disable track state of main interface</help>
+ </properties>
+ </leafNode>
+ <leafNode name="interface">
+ <properties>
+ <help>Interface name state check</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="interface-name"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
#include <include/vrrp-transition-script.xml.i>
- <leafNode name="address">
+ <tagNode name="address">
<properties>
<help>Virtual IP address</help>
<valueHelp>
@@ -193,9 +222,11 @@
<validator name="ipv4-host"/>
<validator name="ipv6-host"/>
</constraint>
- <multi/>
</properties>
- </leafNode>
+ <children>
+ #include <include/generic-interface-broadcast.xml.i>
+ </children>
+ </tagNode>
<leafNode name="excluded-address">
<properties>
<help>Virtual address (If you need additional IPv4 and IPv6 in same group)</help>
@@ -252,6 +283,144 @@
</tagNode>
</children>
</node>
+ <tagNode name="virtual-server">
+ <properties>
+ <help>Load-balancing virtual server address</help>
+ </properties>
+ <children>
+ <leafNode name="algorithm">
+ <properties>
+ <help>Schedule algorithm (default - least-connection)</help>
+ <completionHelp>
+ <list>round-robin weighted-round-robin least-connection weighted-least-connection source-hashing destination-hashing locality-based-least-connection</list>
+ </completionHelp>
+ <valueHelp>
+ <format>round-robin</format>
+ <description>Round robin</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weighted-round-robin</format>
+ <description>Weighted round robin</description>
+ </valueHelp>
+ <valueHelp>
+ <format>least-connection</format>
+ <description>Least connection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weighted-least-connection</format>
+ <description>Weighted least connection</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-hashing</format>
+ <description>Source hashing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>destination-hashing</format>
+ <description>Destination hashing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>locality-based-least-connection</format>
+ <description>Locality-Based least connection</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(round-robin|weighted-round-robin|least-connection|weighted-least-connection|source-hashing|destination-hashing|locality-based-least-connection)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>least-connection</defaultValue>
+ </leafNode>
+ <leafNode name="delay-loop">
+ <properties>
+ <help>Interval between health-checks (in seconds)</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>Interval in seconds (default: 10)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="forward-method">
+ <properties>
+ <help>Forwarding method (default: NAT)</help>
+ <completionHelp>
+ <list>direct nat tunnel</list>
+ </completionHelp>
+ <valueHelp>
+ <format>direct</format>
+ <description>Direct routing</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nat</format>
+ <description>NAT</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tunnel</format>
+ <description>Tunneling</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(direct|nat|tunnel)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>nat</defaultValue>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="persistence-timeout">
+ <properties>
+ <help>Timeout for persistent connections</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Timeout for persistent connections (default: 300)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+ </leafNode>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol for port checks (default: TCP)</help>
+ <completionHelp>
+ <list>tcp udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>TCP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>UDP</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(tcp|udp)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>tcp</defaultValue>
+ </leafNode>
+ <tagNode name="real-server">
+ <properties>
+ <help>Real server address</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="connection-timeout">
+ <properties>
+ <help>Server connection timeout</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Connection timeout to remote server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/include/conntrack/log-common.xml.i b/interface-definitions/include/conntrack/log-common.xml.i
new file mode 100644
index 000000000..38799f8f4
--- /dev/null
+++ b/interface-definitions/include/conntrack/log-common.xml.i
@@ -0,0 +1,20 @@
+<!-- include start from conntrack/log-common.xml.i -->
+<leafNode name="destroy">
+ <properties>
+ <help>Log connection deletion</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="new">
+ <properties>
+ <help>Log connection creation</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="update">
+ <properties>
+ <help>Log connection updates</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/conntrack/timeout-common-protocols.xml.i b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i
new file mode 100644
index 000000000..2676d846e
--- /dev/null
+++ b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i
@@ -0,0 +1,172 @@
+<!-- include start from conntrack/timeout-common-protocols.xml.i -->
+<leafNode name="icmp">
+ <properties>
+ <help>ICMP timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>ICMP timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+</leafNode>
+<leafNode name="other">
+ <properties>
+ <help>Generic connection timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>Generic connection timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>600</defaultValue>
+</leafNode>
+<node name="tcp">
+ <properties>
+ <help>TCP connection timeout options</help>
+ </properties>
+ <children>
+ <leafNode name="close-wait">
+ <properties>
+ <help>TCP CLOSE-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP CLOSE-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="close">
+ <properties>
+ <help>TCP CLOSE timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP CLOSE timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="established">
+ <properties>
+ <help>TCP ESTABLISHED timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP ESTABLISHED timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>432000</defaultValue>
+ </leafNode>
+ <leafNode name="fin-wait">
+ <properties>
+ <help>TCP FIN-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP FIN-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="last-ack">
+ <properties>
+ <help>TCP LAST-ACK timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP LAST-ACK timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="syn-recv">
+ <properties>
+ <help>TCP SYN-RECEIVED timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP SYN-RECEIVED timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="syn-sent">
+ <properties>
+ <help>TCP SYN-SENT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP SYN-SENT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="time-wait">
+ <properties>
+ <help>TCP TIME-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP TIME-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<node name="udp">
+ <properties>
+ <help>UDP timeout options</help>
+ </properties>
+ <children>
+ <leafNode name="other">
+ <properties>
+ <help>UDP generic timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>UDP generic timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="stream">
+ <properties>
+ <help>UDP stream timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>UDP stream timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>180</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i
index 230f590cb..4ba93e3aa 100644
--- a/interface-definitions/include/firewall/action.xml.i
+++ b/interface-definitions/include/firewall/action.xml.i
@@ -3,18 +3,22 @@
<properties>
<help>Rule action [REQUIRED]</help>
<completionHelp>
- <list>permit deny</list>
+ <list>accept reject drop</list>
</completionHelp>
<valueHelp>
- <format>permit</format>
- <description>Permit matching entries</description>
+ <format>accept</format>
+ <description>Accept matching entries</description>
</valueHelp>
<valueHelp>
- <format>deny</format>
- <description>Deny matching entries</description>
+ <format>reject</format>
+ <description>Reject matching entries</description>
+ </valueHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop matching entries</description>
</valueHelp>
<constraint>
- <regex>^(permit|deny)$</regex>
+ <regex>^(accept|reject|drop)$</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index a59c0b390..353804990 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -55,7 +55,7 @@
<help>Maximum number of packets to allow in excess of rate</help>
<valueHelp>
<format>u32:0-4294967295</format>
- <description>burst__change_me</description>
+ <description>Maximum number of packets to allow in excess of rate</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-4294967295"/>
@@ -67,7 +67,7 @@
<help>Maximum average matching rate</help>
<valueHelp>
<format>u32:0-4294967295</format>
- <description>rate__change_me</description>
+ <description>Maximum average matching rate</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-4294967295"/>
@@ -99,7 +99,8 @@
<properties>
<help>Protocol to match (protocol name, number, or "all")</help>
<completionHelp>
- <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
</completionHelp>
<valueHelp>
<format>all</format>
@@ -114,14 +115,17 @@
<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 number</description>
+ <description>IP protocol name</description>
</valueHelp>
<constraint>
<validator name="ip-protocol"/>
</constraint>
</properties>
- <defaultValue>all</defaultValue>
</leafNode>
<node name="recent">
<properties>
@@ -142,13 +146,24 @@
</leafNode>
<leafNode name="time">
<properties>
- <help>Source addresses seen in the last N seconds</help>
+ <help>Source addresses seen in the last second/minute/hour</help>
+ <completionHelp>
+ <list>second minute hour</list>
+ </completionHelp>
<valueHelp>
- <format>u32:0-4294967295</format>
- <description>Source addresses seen in the last N seconds</description>
+ <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>
- <validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>^(second|minute|hour)$</regex>
</constraint>
</properties>
</leafNode>
@@ -172,6 +187,9 @@
<format>!&lt;MAC address&gt;</format>
<description>Match everything except the specified MAC address</description>
</valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
</properties>
</leafNode>
#include <include/firewall/port.xml.i>
@@ -260,65 +278,71 @@
</leafNode>
</children>
</node>
-<node name="tcp">
- <properties>
- <help>TCP flags to match</help>
- </properties>
- <children>
- <leafNode name="flags">
- <properties>
- <help>TCP flags to match</help>
- <valueHelp>
- <format>txt</format>
- <description>TCP flags to match</description>
- </valueHelp>
- <valueHelp>
- <format> </format>
- <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description>
- </valueHelp>
- </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="monthdays">
- <properties>
- <help>Monthdays to match rule on</help>
- </properties>
- </leafNode>
<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>
- </properties>
- </leafNode>
- <leafNode name="utc">
- <properties>
- <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
- <valueless/>
+ <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>Weekdays to match rule on</help>
+ <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>
diff --git a/interface-definitions/include/firewall/icmp-type-name.xml.i b/interface-definitions/include/firewall/icmp-type-name.xml.i
index b45fb619b..f57def3e1 100644
--- a/interface-definitions/include/firewall/icmp-type-name.xml.i
+++ b/interface-definitions/include/firewall/icmp-type-name.xml.i
@@ -3,170 +3,70 @@
<properties>
<help>ICMP type-name</help>
<completionHelp>
- <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply</list>
+ <list>echo-reply destination-unreachable source-quench redirect echo-request router-advertisement router-solicitation time-exceeded parameter-problem timestamp-request timestamp-reply info-request info-reply address-mask-request address-mask-reply</list>
</completionHelp>
<valueHelp>
- <format>any</format>
- <description>Any ICMP type/code</description>
- </valueHelp>
- <valueHelp>
<format>echo-reply</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>pong</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 0: echo-reply</description>
</valueHelp>
<valueHelp>
<format>destination-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>protocol-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>port-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>fragmentation-needed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>source-route-failed</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-unknown</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-host-unreachable</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>communication-prohibited</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-precedence-violation</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>precedence-cutoff</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 3: destination-unreachable</description>
</valueHelp>
<valueHelp>
<format>source-quench</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 4: source-quench</description>
</valueHelp>
<valueHelp>
<format>redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>host-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS-network-redirect</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>TOS host-redirect</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 5: redirect</description>
</valueHelp>
<valueHelp>
<format>echo-request</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ping</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 8: echo-request</description>
</valueHelp>
<valueHelp>
<format>router-advertisement</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 9: router-advertisement</description>
</valueHelp>
<valueHelp>
<format>router-solicitation</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 10: router-solicitation</description>
</valueHelp>
<valueHelp>
<format>time-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-exceeded</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-transit</format>
- <description>ICMP type/code name</description>
- </valueHelp>
- <valueHelp>
- <format>ttl-zero-during-reassembly</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 11: time-exceeded</description>
</valueHelp>
<valueHelp>
<format>parameter-problem</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 12: parameter-problem</description>
</valueHelp>
<valueHelp>
- <format>ip-header-bad</format>
- <description>ICMP type/code name</description>
+ <format>timestamp-request</format>
+ <description>ICMP type 13: timestamp-request</description>
</valueHelp>
<valueHelp>
- <format>required-option-missing</format>
- <description>ICMP type/code name</description>
+ <format>timestamp-reply</format>
+ <description>ICMP type 14: timestamp-reply</description>
</valueHelp>
<valueHelp>
- <format>timestamp-request</format>
- <description>ICMP type/code name</description>
+ <format>info-request</format>
+ <description>ICMP type 15: info-request</description>
</valueHelp>
<valueHelp>
- <format>timestamp-reply</format>
- <description>ICMP type/code name</description>
+ <format>info-reply</format>
+ <description>ICMP type 16: info-reply</description>
</valueHelp>
<valueHelp>
<format>address-mask-request</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 17: address-mask-request</description>
</valueHelp>
<valueHelp>
<format>address-mask-reply</format>
- <description>ICMP type/code name</description>
+ <description>ICMP type 18: address-mask-reply</description>
</valueHelp>
<constraint>
- <regex>^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply)$</regex>
+ <regex>^(echo-reply|destination-unreachable|source-quench|redirect|echo-request|router-advertisement|router-solicitation|time-exceeded|parameter-problem|timestamp-request|timestamp-reply|info-request|info-reply|address-mask-request|address-mask-reply)$</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/icmpv6-type-name.xml.i b/interface-definitions/include/firewall/icmpv6-type-name.xml.i
new file mode 100644
index 000000000..b13cf02c4
--- /dev/null
+++ b/interface-definitions/include/firewall/icmpv6-type-name.xml.i
@@ -0,0 +1,73 @@
+<!-- include start from firewall/icmpv6-type-name.xml.i -->
+<leafNode name="type-name">
+ <properties>
+ <help>ICMPv6 type-name</help>
+ <completionHelp>
+ <list>destination-unreachable packet-too-big time-exceeded echo-request echo-reply mld-listener-query mld-listener-report mld-listener-reduction nd-router-solicit nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect parameter-problem router-renumbering</list>
+ </completionHelp>
+ <valueHelp>
+ <format>destination-unreachable</format>
+ <description>ICMPv6 type 1: destination-unreachable</description>
+ </valueHelp>
+ <valueHelp>
+ <format>packet-too-big</format>
+ <description>ICMPv6 type 2: packet-too-big</description>
+ </valueHelp>
+ <valueHelp>
+ <format>time-exceeded</format>
+ <description>ICMPv6 type 3: time-exceeded</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-request</format>
+ <description>ICMPv6 type 128: echo-request</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-reply</format>
+ <description>ICMPv6 type 129: echo-reply</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mld-listener-query</format>
+ <description>ICMPv6 type 130: mld-listener-query</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mld-listener-report</format>
+ <description>ICMPv6 type 131: mld-listener-report</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mld-listener-reduction</format>
+ <description>ICMPv6 type 132: mld-listener-reduction</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nd-router-solicit</format>
+ <description>ICMPv6 type 133: nd-router-solicit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nd-router-advert</format>
+ <description>ICMPv6 type 134: nd-router-advert</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nd-neighbor-solicit</format>
+ <description>ICMPv6 type 135: nd-neighbor-solicit</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nd-neighbor-advert</format>
+ <description>ICMPv6 type 136: nd-neighbor-advert</description>
+ </valueHelp>
+ <valueHelp>
+ <format>nd-redirect</format>
+ <description>ICMPv6 type 137: nd-redirect</description>
+ </valueHelp>
+ <valueHelp>
+ <format>parameter-problem</format>
+ <description>ICMPv6 type 4: parameter-problem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>router-renumbering</format>
+ <description>ICMPv6 type 138: router-renumbering</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(destination-unreachable|packet-too-big|time-exceeded|echo-request|echo-reply|mld-listener-query|mld-listener-report|mld-listener-reduction|nd-router-solicit|nd-router-advert|nd-neighbor-solicit|nd-neighbor-advert|nd-redirect|parameter-problem|router-renumbering)$</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/mac-group.xml.i b/interface-definitions/include/firewall/mac-group.xml.i
new file mode 100644
index 000000000..dbce3fc88
--- /dev/null
+++ b/interface-definitions/include/firewall/mac-group.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from firewall/mac-group.xml.i -->
+<leafNode name="mac-group">
+ <properties>
+ <help>Group of MAC addresses</help>
+ <completionHelp>
+ <path>firewall group mac-group</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include start from firewall/mac-group.xml.i --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/port.xml.i b/interface-definitions/include/firewall/port.xml.i
index 59d92978b..3bacafff8 100644
--- a/interface-definitions/include/firewall/port.xml.i
+++ b/interface-definitions/include/firewall/port.xml.i
@@ -16,8 +16,11 @@
</valueHelp>
<valueHelp>
<format> </format>
- <description>\n\n Multiple destination ports can be specified as a comma-separated list.\n The whole list can also be negated using '!'.\n For example: '!22,telnet,http,123,1001-1005'</description>
+ <description>\n\n Multiple destination ports can be specified as a comma-separated list.\n For example: 'telnet,http,123,1001-1005'</description>
</valueHelp>
+ <constraint>
+ <validator name="port-multi"/>
+ </constraint>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
new file mode 100644
index 000000000..c2cc7edb3
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from firewall/source-destination-group-ipv6.xml.i -->
+<node name="group">
+ <properties>
+ <help>Group</help>
+ </properties>
+ <children>
+ <leafNode name="address-group">
+ <properties>
+ <help>Group of addresses</help>
+ <completionHelp>
+ <path>firewall group ipv6-address-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/firewall/mac-group.xml.i>
+ <leafNode name="network-group">
+ <properties>
+ <help>Group of networks</help>
+ <completionHelp>
+ <path>firewall group ipv6-network-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="port-group">
+ <properties>
+ <help>Group of ports</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i
index 30226b0d8..ab11e89e9 100644
--- a/interface-definitions/include/firewall/source-destination-group.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group.xml.i
@@ -7,16 +7,26 @@
<leafNode name="address-group">
<properties>
<help>Group of addresses</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
</properties>
</leafNode>
+ #include <include/firewall/mac-group.xml.i>
<leafNode name="network-group">
<properties>
<help>Group of networks</help>
+ <completionHelp>
+ <path>firewall group network-group</path>
+ </completionHelp>
</properties>
</leafNode>
<leafNode name="port-group">
<properties>
<help>Group of ports</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
new file mode 100644
index 000000000..b99896687
--- /dev/null
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -0,0 +1,119 @@
+<!-- include start from firewall/tcp-flags.xml.i -->
+<node name="tcp">
+ <properties>
+ <help>TCP flags to match</help>
+ </properties>
+ <children>
+ <node name="flags">
+ <properties>
+ <help>TCP flags to match</help>
+ </properties>
+ <children>
+ <leafNode name="syn">
+ <properties>
+ <help>Synchronise flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ack">
+ <properties>
+ <help>Acknowledge flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="fin">
+ <properties>
+ <help>Finish flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rst">
+ <properties>
+ <help>Reset flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="urg">
+ <properties>
+ <help>Urgent flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="psh">
+ <properties>
+ <help>Push flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ecn">
+ <properties>
+ <help>Explicit Congestion Notification flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cwr">
+ <properties>
+ <help>Congestion Window Reduced flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="not">
+ <properties>
+ <help>Match flags not set</help>
+ </properties>
+ <children>
+ <leafNode name="syn">
+ <properties>
+ <help>Synchronise flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ack">
+ <properties>
+ <help>Acknowledge flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="fin">
+ <properties>
+ <help>Finish flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rst">
+ <properties>
+ <help>Reset flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="urg">
+ <properties>
+ <help>Urgent flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="psh">
+ <properties>
+ <help>Push flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ecn">
+ <properties>
+ <help>Explicit Congestion Notification flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cwr">
+ <properties>
+ <help>Congestion Window Reduced flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i
new file mode 100644
index 000000000..5a8d47280
--- /dev/null
+++ b/interface-definitions/include/interface/inbound-interface.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from interface/inbound-interface.xml.i -->
+<leafNode name="inbound-interface">
+ <properties>
+ <help>Inbound Interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i b/interface-definitions/include/interface/interface-firewall-vif-c.xml.i
new file mode 100644
index 000000000..1bc235fcb
--- /dev/null
+++ b/interface-definitions/include/interface/interface-firewall-vif-c.xml.i
@@ -0,0 +1,79 @@
+<!-- include start from interface/interface-firewall-vif-c.xml.i -->
+<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)">
+ <properties>
+ <priority>615</priority>
+ <help>Firewall options</help>
+ </properties>
+ <children>
+ <node name="in">
+ <properties>
+ <help>forwarded packets on inbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Inbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Inbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="out">
+ <properties>
+ <help>forwarded packets on outbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Outbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Outbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="local">
+ <properties>
+ <help>packets destined for this router</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Local IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Local IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall-vif.xml.i b/interface-definitions/include/interface/interface-firewall-vif.xml.i
new file mode 100644
index 000000000..a37ac5c4a
--- /dev/null
+++ b/interface-definitions/include/interface/interface-firewall-vif.xml.i
@@ -0,0 +1,79 @@
+<!-- include start from interface/interface-firewall-vif.xml.i -->
+<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../@).$VAR(../@)">
+ <properties>
+ <priority>615</priority>
+ <help>Firewall options</help>
+ </properties>
+ <children>
+ <node name="in">
+ <properties>
+ <help>forwarded packets on inbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Inbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Inbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="out">
+ <properties>
+ <help>forwarded packets on outbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Outbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Outbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="local">
+ <properties>
+ <help>packets destined for this router</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Local IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Local IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-firewall.xml.i b/interface-definitions/include/interface/interface-firewall.xml.i
new file mode 100644
index 000000000..b3f20c3bf
--- /dev/null
+++ b/interface-definitions/include/interface/interface-firewall.xml.i
@@ -0,0 +1,79 @@
+<!-- include start from interface/interface-firewall.xml.i -->
+<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../@)">
+ <properties>
+ <priority>615</priority>
+ <help>Firewall options</help>
+ </properties>
+ <children>
+ <node name="in">
+ <properties>
+ <help>forwarded packets on inbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Inbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Inbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="out">
+ <properties>
+ <help>forwarded packets on outbound interface</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Outbound IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Outbound IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="local">
+ <properties>
+ <help>packets destined for this router</help>
+ </properties>
+ <children>
+ <leafNode name="name">
+ <properties>
+ <help>Local IPv4 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-name">
+ <properties>
+ <help>Local IPv6 firewall ruleset name for interface</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i
new file mode 100644
index 000000000..866fcd5c0
--- /dev/null
+++ b/interface-definitions/include/interface/interface-policy-vif-c.xml.i
@@ -0,0 +1,26 @@
+<!-- include start from interface/interface-policy-vif-c.xml.i -->
+<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)">
+ <properties>
+ <priority>620</priority>
+ <help>Policy route options</help>
+ </properties>
+ <children>
+ <leafNode name="route">
+ <properties>
+ <help>IPv4 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="route6">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i
new file mode 100644
index 000000000..83510fe59
--- /dev/null
+++ b/interface-definitions/include/interface/interface-policy-vif.xml.i
@@ -0,0 +1,26 @@
+<!-- include start from interface/interface-policy-vif.xml.i -->
+<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../../@).$VAR(../@)">
+ <properties>
+ <priority>620</priority>
+ <help>Policy route options</help>
+ </properties>
+ <children>
+ <leafNode name="route">
+ <properties>
+ <help>IPv4 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="route6">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i
new file mode 100644
index 000000000..42a8fd009
--- /dev/null
+++ b/interface-definitions/include/interface/interface-policy.xml.i
@@ -0,0 +1,26 @@
+<!-- include start from interface/interface-policy.xml.i -->
+<node name="policy" owner="${vyos_conf_scripts_dir}/policy-route-interface.py $VAR(../@)">
+ <properties>
+ <priority>620</priority>
+ <help>Policy route options</help>
+ </properties>
+ <children>
+ <leafNode name="route">
+ <properties>
+ <help>IPv4 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="route6">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy route6</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index e7ba6d193..f1a61ff64 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -18,6 +18,8 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
+ #include <include/interface/interface-firewall-vif.xml.i>
+ #include <include/interface/interface-policy-vif.xml.i>
<leafNode name="protocol">
<properties>
<help>Protocol used for service VLAN (default: 802.1ad)</help>
@@ -63,6 +65,8 @@
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/vrf.xml.i>
+ #include <include/interface/interface-firewall-vif-c.xml.i>
+ #include <include/interface/interface-policy-vif-c.xml.i>
</children>
</tagNode>
#include <include/interface/vrf.xml.i>
diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i
index 5644c554f..11ba7e2f8 100644
--- a/interface-definitions/include/interface/vif.xml.i
+++ b/interface-definitions/include/interface/vif.xml.i
@@ -19,6 +19,8 @@
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
+ #include <include/interface/interface-firewall-vif.xml.i>
+ #include <include/interface/interface-policy-vif.xml.i>
<leafNode name="egress-qos">
<properties>
<help>VLAN egress QoS</help>
diff --git a/interface-definitions/include/listen-address.xml.i b/interface-definitions/include/listen-address.xml.i
index 19ddf0bb0..48003dbf2 100644
--- a/interface-definitions/include/listen-address.xml.i
+++ b/interface-definitions/include/listen-address.xml.i
@@ -17,6 +17,7 @@
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv6-address"/>
+ <validator name="ipv6-link-local"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/nat-port.xml.i b/interface-definitions/include/nat-port.xml.i
index ebba43712..5f762cfb3 100644
--- a/interface-definitions/include/nat-port.xml.i
+++ b/interface-definitions/include/nat-port.xml.i
@@ -3,6 +3,10 @@
<properties>
<help>Port number</help>
<valueHelp>
+ <format>txt</format>
+ <description>Named port (any name in /etc/services, e.g., http)</description>
+ </valueHelp>
+ <valueHelp>
<format>u32:1-65535</format>
<description>Numeric IP port</description>
</valueHelp>
@@ -11,9 +15,12 @@
<description>Numbered port range (e.g. 1001-1005)</description>
</valueHelp>
<valueHelp>
- <format> </format>
+ <format/>
<description>\n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005'</description>
</valueHelp>
+ <constraint>
+ <validator name="port-multi"/>
+ </constraint>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index 084f1f722..bdb86ed9b 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -4,7 +4,7 @@
<help>Rule number for NAT</help>
<valueHelp>
<format>u32:1-999999</format>
- <description>Number for this NAT rule</description>
+ <description>Number of NAT rule</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
@@ -12,11 +12,7 @@
<constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Rule description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<node name="destination">
<properties>
<help>NAT destination parameters</help>
diff --git a/interface-definitions/include/nat-translation-port.xml.i b/interface-definitions/include/nat-translation-port.xml.i
index 6e507353c..6f17df3d9 100644
--- a/interface-definitions/include/nat-translation-port.xml.i
+++ b/interface-definitions/include/nat-translation-port.xml.i
@@ -10,6 +10,9 @@
<format>range</format>
<description>Numbered port range (e.g., 1001-1005)</description>
</valueHelp>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index 688e78034..e783f4bec 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -289,6 +289,18 @@
</constraint>
</properties>
</leafNode>
+<leafNode name="maximum-paths">
+ <properties>
+ <help>Maximum multiple paths (ECMP)</help>
+ <valueHelp>
+ <format>u32:1-64</format>
+ <description>Maximum multiple paths (ECMP)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-64"/>
+ </constraint>
+ </properties>
+</leafNode>
<node name="distance">
<properties>
<help>Administrative distance</help>
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
new file mode 100644
index 000000000..406125e55
--- /dev/null
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -0,0 +1,553 @@
+<!-- include start from policy/route-common-rule.xml.i -->
+#include <include/policy/route-rule-action.xml.i>
+#include <include/generic-description.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>u32:0-4294967295</format>
+ <description>Maximum average matching rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </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="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
+ </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>0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</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 N seconds</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Source addresses seen in the last N seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="dscp">
+ <properties>
+ <help>Packet Differentiated Services Codepoint (DSCP)</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mark">
+ <properties>
+ <help>Packet marking</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Packet marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>main</format>
+ <description>Main table</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+ <regex>^(main)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-mss">
+ <properties>
+ <help>TCP Maximum Segment Size</help>
+ <valueHelp>
+ <format>u32:500-1460</format>
+ <description>Explicitly set TCP MSS value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 500-1460"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ <leafNode name="mac-address">
+ <properties>
+ <help>Source MAC address</help>
+ <valueHelp>
+ <format>&lt;MAC address&gt;</format>
+ <description>MAC address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;MAC address&gt;</format>
+ <description>Match everything except the specified MAC address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/port.xml.i>
+ </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="monthdays">
+ <properties>
+ <help>Monthdays to match rule on</help>
+ </properties>
+ </leafNode>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="utc">
+ <properties>
+ <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Weekdays to match rule on</help>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="icmpv6">
+ <properties>
+ <help>ICMPv6 type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="type">
+ <properties>
+ <help>ICMP type-name</help>
+ <completionHelp>
+ <list>any echo-reply pong destination-unreachable network-unreachable host-unreachable protocol-unreachable port-unreachable fragmentation-needed source-route-failed network-unknown host-unknown network-prohibited host-prohibited TOS-network-unreachable TOS-host-unreachable communication-prohibited host-precedence-violation precedence-cutoff source-quench redirect network-redirect host-redirect TOS-network-redirect TOS host-redirect echo-request ping router-advertisement router-solicitation time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly parameter-problem ip-header-bad required-option-missing timestamp-request timestamp-reply address-mask-request address-mask-reply packet-too-big</list>
+ </completionHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Any ICMP type/code</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>pong</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>destination-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocol-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>port-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>fragmentation-needed</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-route-failed</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-unknown</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-unknown</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-network-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-host-unreachable</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>communication-prohibited</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-precedence-violation</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>precedence-cutoff</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>source-quench</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>host-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS-network-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>TOS host-redirect</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>echo-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ping</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>router-advertisement</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>router-solicitation</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>time-exceeded</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-exceeded</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-zero-during-transit</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ttl-zero-during-reassembly</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>parameter-problem</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ip-header-bad</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>required-option-missing</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>timestamp-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>timestamp-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>address-mask-request</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>address-mask-reply</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>packet-too-big</format>
+ <description>ICMP type/code name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(any|echo-reply|pong|destination-unreachable|network-unreachable|host-unreachable|protocol-unreachable|port-unreachable|fragmentation-needed|source-route-failed|network-unknown|host-unknown|network-prohibited|host-prohibited|TOS-network-unreachable|TOS-host-unreachable|communication-prohibited|host-precedence-violation|precedence-cutoff|source-quench|redirect|network-redirect|host-redirect|TOS-network-redirect|TOS host-redirect|echo-request|ping|router-advertisement|router-solicitation|time-exceeded|ttl-exceeded|ttl-zero-during-transit|ttl-zero-during-reassembly|parameter-problem|ip-header-bad|required-option-missing|timestamp-request|timestamp-reply|address-mask-request|address-mask-reply|packet-too-big)$</regex>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i
new file mode 100644
index 000000000..33c4ba77c
--- /dev/null
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -0,0 +1,402 @@
+<!-- include start from policy/route-common-rule.xml.i -->
+#include <include/policy/route-rule-action.xml.i>
+#include <include/generic-description.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>u32:0-4294967295</format>
+ <description>Maximum average matching rate</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </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="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
+ </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>0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</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 N seconds</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Source addresses seen in the last N seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="dscp">
+ <properties>
+ <help>Packet Differentiated Services Codepoint (DSCP)</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mark">
+ <properties>
+ <help>Packet marking</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Packet marking</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>main</format>
+ <description>Main table</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+ <regex>^(main)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-mss">
+ <properties>
+ <help>TCP Maximum Segment Size</help>
+ <valueHelp>
+ <format>u32:500-1460</format>
+ <description>Explicitly set TCP MSS value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 500-1460"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ <leafNode name="mac-address">
+ <properties>
+ <help>Source MAC address</help>
+ <valueHelp>
+ <format>&lt;MAC address&gt;</format>
+ <description>MAC address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;MAC address&gt;</format>
+ <description>Match everything except the specified MAC address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/firewall/port.xml.i>
+ </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="monthdays">
+ <properties>
+ <help>Monthdays to match rule on</help>
+ </properties>
+ </leafNode>
+ <leafNode name="startdate">
+ <properties>
+ <help>Date to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="starttime">
+ <properties>
+ <help>Time of day to start matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stopdate">
+ <properties>
+ <help>Date to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="stoptime">
+ <properties>
+ <help>Time of day to stop matching rule</help>
+ </properties>
+ </leafNode>
+ <leafNode name="utc">
+ <properties>
+ <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="weekdays">
+ <properties>
+ <help>Weekdays to match rule on</help>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="icmp">
+ <properties>
+ <help>ICMP type and code information</help>
+ </properties>
+ <children>
+ <leafNode name="code">
+ <properties>
+ <help>ICMP code (0-255)</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 (0-255)</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>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-rule-action.xml.i b/interface-definitions/include/policy/route-rule-action.xml.i
new file mode 100644
index 000000000..9c880579d
--- /dev/null
+++ b/interface-definitions/include/policy/route-rule-action.xml.i
@@ -0,0 +1,17 @@
+<!-- include start from policy/route-rule-action.xml.i -->
+<leafNode name="action">
+ <properties>
+ <help>Rule action [REQUIRED]</help>
+ <completionHelp>
+ <list>drop</list>
+ </completionHelp>
+ <valueHelp>
+ <format>drop</format>
+ <description>Drop matching entries</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(drop)$</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/ssh-user.xml.i b/interface-definitions/include/ssh-user.xml.i
index 677602dd8..17ba05a90 100644
--- a/interface-definitions/include/ssh-user.xml.i
+++ b/interface-definitions/include/ssh-user.xml.i
@@ -3,9 +3,9 @@
<properties>
<help>Allow specific users to login</help>
<constraint>
- <regex>[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ <regex>^[-_a-zA-Z0-9.]{1,100}</regex>
</constraint>
- <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage>
+ <constraintErrorMessage>Illegal characters or more than 100 characters</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 17879cf1e..723041ca5 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -56,6 +56,8 @@
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mirror.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="hash-policy">
<properties>
<help>Bonding transmit hash policy</help>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index 144f43f32..89a6d2303 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -41,6 +41,8 @@
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="forwarding-delay">
<properties>
<help>Forwarding delay</help>
@@ -57,7 +59,7 @@
</leafNode>
<leafNode name="hello-time">
<properties>
- <help>Hello packet advertisment interval</help>
+ <help>Hello packet advertisement interval</help>
<valueHelp>
<format>u32:1-10</format>
<description>Spanning Tree Protocol hello advertisement interval in seconds (default 2)</description>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index 4d4c44160..3bca8b950 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -19,6 +19,8 @@
#include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<node name="ip">
<properties>
<help>IPv4 routing parameters</help>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index ceeda12a0..9e113cb71 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -31,6 +31,8 @@
</leafNode>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="duplex">
<properties>
<help>Duplex mode</help>
diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in
index 2ca7dd9f6..dd4d324d4 100644
--- a/interface-definitions/interfaces-geneve.xml.in
+++ b/interface-definitions/interfaces-geneve.xml.in
@@ -23,6 +23,8 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1450-16000.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<node name="parameters">
<properties>
<help>GENEVE tunnel parameters</help>
diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in
index 9364c85cd..85d4ab992 100644
--- a/interface-definitions/interfaces-l2tpv3.xml.in
+++ b/interface-definitions/interfaces-l2tpv3.xml.in
@@ -32,6 +32,8 @@
<defaultValue>5000</defaultValue>
</leafNode>
#include <include/interface/disable.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="encapsulation">
<properties>
<help>Encapsulation type (default: UDP)</help>
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index 4a566ef8b..d69a093af 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -19,6 +19,8 @@
#include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<node name="security">
<properties>
<help>Security/Encryption Settings</help>
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 6b4440688..16d91145f 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -34,6 +34,8 @@
</children>
</node>
#include <include/interface/description.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="device-type">
<properties>
<help>OpenVPN interface device-type (default: tun)</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 57bb01258..80a890940 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -19,6 +19,8 @@
#include <include/pppoe-access-concentrator.xml.i>
#include <include/interface/authentication.xml.i>
#include <include/interface/dial-on-demand.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="default-route">
<properties>
<help>Default route insertion behaviour (default: auto)</help>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 366892032..bf7055f8d 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -27,6 +27,8 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/source-interface-ethernet.xml.i>
#include <include/interface/mac.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="mode">
<properties>
<help>Receive mode (default: private)</help>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 203685790..fd69fd177 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -30,6 +30,8 @@
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/source-interface.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="6rd-prefix">
<properties>
<help>6rd network prefix</help>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index b12434ae7..f03c7476d 100644
--- a/interface-definitions/interfaces-vti.xml.in
+++ b/interface-definitions/interfaces-vti.xml.in
@@ -35,6 +35,8 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
#include <include/interface/vrf.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 6c53f8c44..4c3c3ac71 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -53,6 +53,8 @@
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
#include <include/interface/mtu-1200-16000.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="mtu">
<defaultValue>1450</defaultValue>
</leafNode>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 403282e5c..1b4b4a816 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -22,6 +22,8 @@
#include <include/interface/vrf.xml.i>
#include <include/port-number.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<leafNode name="mtu">
<defaultValue>1420</defaultValue>
</leafNode>
@@ -99,6 +101,7 @@
</valueHelp>
<constraint>
<validator name="ip-address"/>
+ <validator name="ipv6-link-local"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index 048c7b475..a2d1439a3 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -17,6 +17,8 @@
</properties>
<children>
#include <include/interface/address-ipv4-ipv6-dhcp.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
<node name="capabilities">
<properties>
<help>HT and VHT capabilities for your card</help>
diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in
index 6b6fa1a66..03554feed 100644
--- a/interface-definitions/interfaces-wwan.xml.in
+++ b/interface-definitions/interfaces-wwan.xml.in
@@ -39,6 +39,8 @@
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/dial-on-demand.xml.i>
+ #include <include/interface/interface-firewall.xml.i>
+ #include <include/interface/interface-policy.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in
index 86445b65d..573a7963f 100644
--- a/interface-definitions/policy-local-route.xml.in
+++ b/interface-definitions/policy-local-route.xml.in
@@ -14,7 +14,7 @@
<valueHelp>
<!-- table main with prio 32766 -->
<format>u32:1-32765</format>
- <description>Local-route rule number (1-219)</description>
+ <description>Local-route rule number (1-32765)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-32765"/>
@@ -70,6 +70,115 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination address or prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/interface/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py">
+ <properties>
+ <help>IPv6 policy route of local traffic</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>IPv6 policy local-route rule set number</help>
+ <valueHelp>
+ <!-- table main with prio 32766 -->
+ <format>u32:1-32765</format>
+ <description>Local-route rule number (1-32765)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-32765"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table to forward packet with</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <completionHelp>
+ <list>main</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="fwmark">
+ <properties>
+ <help>Match fwmark value</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="source">
+ <properties>
+ <help>Source address or prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="destination">
+ <properties>
+ <help>Destination address or prefix</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Address to match against</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Prefix to match against</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/interface/inbound-interface.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in
new file mode 100644
index 000000000..a1c3b50de
--- /dev/null
+++ b/interface-definitions/policy-route.xml.in
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="policy">
+ <children>
+ <tagNode name="route6" owner="${vyos_conf_scripts_dir}/policy-route.py">
+ <properties>
+ <help>Policy route rule set name for IPv6</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
+ <priority>201</priority>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ #include <include/firewall/name-default-log.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Policy rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of policy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address-ipv6.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+ </node>
+ #include <include/policy/route-common-rule-ipv6.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="route" owner="${vyos_conf_scripts_dir}/policy-route.py">
+ <properties>
+ <help>Policy route rule set name for IPv4</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
+ <priority>201</priority>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ #include <include/firewall/name-default-log.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Policy rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of policy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/firewall/address.xml.i>
+ #include <include/firewall/source-destination-group.xml.i>
+ #include <include/firewall/port.xml.i>
+ </children>
+ </node>
+ #include <include/policy/route-common-rule.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 225f9a6f9..61c5ab90a 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -793,7 +793,7 @@
</node>
<leafNode name="local-preference">
<properties>
- <help>local-preference_help</help>
+ <help>Local Preference</help>
<valueHelp>
<format>u32:0-4294967295</format>
<description>Local Preference</description>
diff --git a/interface-definitions/service_monitoring_telegraf.xml.in b/interface-definitions/service_monitoring_telegraf.xml.in
new file mode 100644
index 000000000..0db9052ff
--- /dev/null
+++ b/interface-definitions/service_monitoring_telegraf.xml.in
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="monitoring">
+ <properties>
+ <help>Monitoring services</help>
+ <priority>1280</priority>
+ </properties>
+ <children>
+ <node name="telegraf" owner="${vyos_conf_scripts_dir}/service_monitoring_telegraf.py">
+ <properties>
+ <help>Telegraf monitoring</help>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication parameters</help>
+ </properties>
+ <children>
+ <leafNode name="organization">
+ <properties>
+ <help>Authentication organization for InfluxDB v2 [REQUIRED]</help>
+ <constraint>
+ <regex>^[a-zA-Z][1-9a-zA-Z@_\-.]{2,50}$</regex>
+ </constraint>
+ <constraintErrorMessage>Organization name must be alphanumeric and can contain hyphens, underscores and at symbol.</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="token">
+ <properties>
+ <help>Authentication token for InfluxDB v2 [REQUIRED]</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Authentication token</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[a-zA-Z0-9-_]{86}==$</regex>
+ </constraint>
+ <constraintErrorMessage>Token must be 88 characters long and must contain only [a-zA-Z0-9-_] and '==' characters.</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="bucket">
+ <properties>
+ <help>Remote bucket, by default (main)</help>
+ </properties>
+ <defaultValue>main</defaultValue>
+ </leafNode>
+ <leafNode name="source">
+ <properties>
+ <help>Source parameters for monitoring (default: all)</help>
+ <completionHelp>
+ <list>all hardware-utilization logs network system telegraf</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All parameters (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hardware-utilization</format>
+ <description>Hardware-utilization parameters (CPU, disk, memory)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>logs</format>
+ <description>Logs parameters</description>
+ </valueHelp>
+ <valueHelp>
+ <format>network</format>
+ <description>Network parameters (net, netstat, nftables)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>system</format>
+ <description>System parameters (system, processes, interrupts)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>telegraf</format>
+ <description>Telegraf internal statistics</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(all|hardware-utilization|logs|network|system|telegraf)$</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ <defaultValue>all</defaultValue>
+ </leafNode>
+ <leafNode name="url">
+ <properties>
+ <help>Remote URL [REQUIRED]</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Remote URL to InfluxDB v2</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}?(\/.*)?$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format.</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Remote port (default: 8086)</help>
+ </properties>
+ <defaultValue>8086</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in
new file mode 100644
index 000000000..7cfe1f02e
--- /dev/null
+++ b/interface-definitions/service_upnp.xml.in
@@ -0,0 +1,224 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="upnp" owner="${vyos_conf_scripts_dir}/service_upnp.py">
+ <properties>
+ <help>Universal Plug and Play (UPnP) service</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <leafNode name="friendly-name">
+ <properties>
+ <help>Name of this service</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Friendly name</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="wan-interface">
+ <properties>
+ <help>WAN network interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <constraint>
+ <validator name="interface-name" />
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="wan-ip">
+ <properties>
+ <help>WAN network IP</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address" />
+ <validator name="ipv6-address" />
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="nat-pmp">
+ <properties>
+ <help>Enable NAT-PMP support</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ <leafNode name="secure-mode">
+ <properties>
+ <help>Enable Secure Mode</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ <leafNode name="presentation-url">
+ <properties>
+ <help>Presentation Url</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Presentation Url</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <node name="pcp-lifetime">
+ <properties>
+ <help>PCP-base lifetime Option</help>
+ </properties>
+ <children>
+ <leafNode name="max">
+ <properties>
+ <help>Max lifetime time</help>
+ <constraint>
+ <validator name="numeric" />
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="min">
+ <properties>
+ <help>Min lifetime time</help>
+ <constraint>
+ <validator name="numeric" />
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="listen">
+ <properties>
+ <help>Local IP addresses for service to listen on</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ <valueHelp>
+ <format>&lt;interface&gt;</format>
+ <description>Monitor interface address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address to listen for incoming connections</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4-prefix</format>
+ <description>IP prefix to listen for incoming connections</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IP address to listen for incoming connections</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6-prefix</format>
+ <description>IP prefix to listen for incoming connections</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="interface-name" />
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-address"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="stun">
+ <properties>
+ <help>Enable STUN probe support (can be used with NAT 1:1 support for WAN interfaces)</help>
+ </properties>
+ <children>
+ <leafNode name="host">
+ <properties>
+ <help>The STUN server address</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>The STUN server host address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ </children>
+ </node>
+ <tagNode name="rule">
+ <properties>
+ <help>UPnP Rule</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Rule number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="external-port-range">
+ <properties>
+ <help>Port range (REQUIRE)</help>
+ <valueHelp>
+ <format>&lt;port&gt;</format>
+ <description>single port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;portN&gt;-&lt;portM&gt;</format>
+ <description>Port range (use '-' as delimiter)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="internal-port-range">
+ <properties>
+ <help>Port range (REQUIRE)</help>
+ <valueHelp>
+ <format>&lt;port&gt;</format>
+ <description>single port</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;portN&gt;-&lt;portM&gt;</format>
+ <description>Port range (use '-' as delimiter)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="ip">
+ <properties>
+ <help>The IP to which this rule applies (REQUIRE)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>The IPv4 to which this rule applies</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address" />
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="action">
+ <properties>
+ <help>Actions against the rule (REQUIRE)</help>
+ <completionHelp>
+ <list>allow deny</list>
+ </completionHelp>
+ <constraint>
+ <regex>^(allow|deny)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index daa4177c9..65edab839 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -35,6 +35,128 @@
</properties>
<defaultValue>32768</defaultValue>
</leafNode>
+ <node name="ignore">
+ <properties>
+ <help>Customized rules to ignore selective connection tracking</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <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="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="log">
+ <properties>
+ <help>Log connection tracking events per protocol</help>
+ </properties>
+ <children>
+ <node name="icmp">
+ <properties>
+ <help>Log connection tracking events for ICMP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="other">
+ <properties>
+ <help>Log connection tracking events for all protocols other than TCP, UDP and ICMP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="tcp">
+ <properties>
+ <help>Log connection tracking events for TCP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="udp">
+ <properties>
+ <help>Log connection tracking events for UDP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="modules">
<properties>
<help>Connection tracking modules</help>
@@ -155,176 +277,66 @@
<help>Connection timeout options</help>
</properties>
<children>
- <leafNode name="icmp">
- <properties>
- <help>ICMP timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>ICMP timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="other">
- <properties>
- <help>Generic connection timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>Generic connection timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>600</defaultValue>
- </leafNode>
- <node name="tcp">
- <properties>
- <help>TCP connection timeout options</help>
- </properties>
- <children>
- <leafNode name="close-wait">
- <properties>
- <help>TCP CLOSE-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP CLOSE-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>60</defaultValue>
- </leafNode>
- <leafNode name="close">
- <properties>
- <help>TCP CLOSE timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP CLOSE timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>10</defaultValue>
- </leafNode>
- <leafNode name="established">
- <properties>
- <help>TCP ESTABLISHED timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP ESTABLISHED timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>432000</defaultValue>
- </leafNode>
- <leafNode name="fin-wait">
- <properties>
- <help>TCP FIN-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP FIN-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- <leafNode name="last-ack">
- <properties>
- <help>TCP LAST-ACK timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP LAST-ACK timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="syn-recv">
- <properties>
- <help>TCP SYN-RECEIVED timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP SYN-RECEIVED timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>60</defaultValue>
- </leafNode>
- <leafNode name="syn-sent">
- <properties>
- <help>TCP SYN-SENT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP SYN-SENT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- <leafNode name="time-wait">
- <properties>
- <help>TCP TIME-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP TIME-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- </children>
- </node>
- <node name="udp">
+ <node name="custom">
<properties>
- <help>UDP timeout options</help>
+ <help>Define custom timeouts per connection</help>
</properties>
<children>
- <leafNode name="other">
+ <tagNode name="rule">
<properties>
- <help>UDP generic timeout in seconds</help>
+ <help>Rule number</help>
<valueHelp>
- <format>u32:1-21474836</format>
- <description>UDP generic timeout in seconds</description>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack rule</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="stream">
- <properties>
- <help>UDP stream timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>UDP stream timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>180</defaultValue>
- </leafNode>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <node name="protocol">
+ <properties>
+ <help>Customize protocol specific timers, one protocol configuration per rule</help>
+ </properties>
+ <children>
+ #include <include/conntrack/timeout-common-protocols.xml.i>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
+ #include <include/conntrack/timeout-common-protocols.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 17ba83bae..afa3d52a0 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -286,7 +286,7 @@
</node>
<leafNode name="ikev2-reauth">
<properties>
- <help>ikev2-reauth_help</help>
+ <help>Re-authentication of the remote peer during an IKE re-key. IKEv2 option only</help>
<completionHelp>
<list>yes no</list>
</completionHelp>
@@ -1047,6 +1047,18 @@
#include <include/ipsec/esp-group.xml.i>
#include <include/ipsec/local-traffic-selector.xml.i>
#include <include/ip-protocol.xml.i>
+ <leafNode name="priority">
+ <properties>
+ <help>Priority for IPSec policy (lowest value more preferable)</help>
+ <valueHelp>
+ <format>u32:1-100</format>
+ <description>Priority for IPSec policy (lowest value more preferable)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100"/>
+ </constraint>
+ </properties>
+ </leafNode>
<node name="remote">
<properties>
<help>Match remote addresses</help>
diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in
new file mode 100644
index 000000000..69ee031c7
--- /dev/null
+++ b/interface-definitions/zone-policy.xml.in
@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="zone-policy" owner="${vyos_conf_scripts_dir}/zone_policy.py">
+ <properties>
+ <help>Configure zone-policy</help>
+ <priority>250</priority>
+ </properties>
+ <children>
+ <tagNode name="zone">
+ <properties>
+ <help>Zone name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Zone name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[a-zA-Z0-9][\w\-\.]*$</regex>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/generic-description.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 (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>reject</format>
+ <description>Drop and notify source</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(drop|reject)$</regex>
+ </constraint>
+ </properties>
+ </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>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </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 (default)</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>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/firewall.xml.in b/op-mode-definitions/firewall.xml.in
new file mode 100644
index 000000000..b5dee7c9e
--- /dev/null
+++ b/op-mode-definitions/firewall.xml.in
@@ -0,0 +1,191 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+<!--
+ <node name="clear">
+ <children>
+ <node name="firewall">
+ <properties>
+ <help>Clear firewall statistics</help>
+ </properties>
+ <children>
+ <tagNode name="ipv6-name">
+ <properties>
+ <help>Clear firewall statistics for chain</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified chain</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Clear firewall statistics for a rule</help>
+ <completionHelp>
+ <path>firewall ipv6-name ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified rule</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="name">
+ <properties>
+ <help>Clear firewall statistics for chain</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified chain</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Clear firewall statistics for a rule</help>
+ <completionHelp>
+ <path>firewall name ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified rule</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+-->
+<!--
+ <node name="reset">
+ <children>
+ <node name="firewall">
+ <properties>
+ <help>Reset a firewall group</help>
+ </properties>
+ <children>
+ <tagNode name="address-group">
+ <properties>
+ <help>Reset a firewall address group</help>
+ </properties>
+ </tagNode>
+ <tagNode name="network-group">
+ <properties>
+ <help>Reset a firewall network group</help>
+ </properties>
+ </tagNode>
+ <tagNode name="port-group">
+ <properties>
+ <help>Reset a firewall port group</help>
+ </properties>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+-->
+ <node name="show">
+ <children>
+ <node name="firewall">
+ <properties>
+ <help>Show firewall information</help>
+ </properties>
+ <children>
+ <tagNode name="group">
+ <properties>
+ <help>Show firewall group</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ <path>firewall group network-group</path>
+ <path>firewall group port-group</path>
+ <path>firewall group ipv6-address-group</path>
+ <path>firewall group ipv6-network-group</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group --name $4</command>
+ </tagNode>
+ <leafNode name="group">
+ <properties>
+ <help>Show firewall group</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group</command>
+ </leafNode>
+ <tagNode name="ipv6-name">
+ <properties>
+ <help>Show IPv6 firewall chains</help>
+ <completionHelp>
+ <path>firewall ipv6-name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6-name ${COMP_WORDS[6]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --rule $6 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --ipv6</command>
+ </tagNode>
+ <tagNode name="name">
+ <properties>
+ <help>Show IPv4 firewall chains</help>
+ <completionHelp>
+ <path>firewall name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 firewall rules</help>
+ <completionHelp>
+ <path>firewall name ${COMP_WORDS[6]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4 --rule $6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --name $4</command>
+ </tagNode>
+ <leafNode name="statistics">
+ <properties>
+ <help>Show statistics of firewall application</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_statistics</command>
+ </leafNode>
+ <leafNode name="summary">
+ <properties>
+ <help>Show summary of firewall application</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_summary</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_all</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/policy-route.xml.in b/op-mode-definitions/policy-route.xml.in
new file mode 100644
index 000000000..bd4a61dc9
--- /dev/null
+++ b/op-mode-definitions/policy-route.xml.in
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+<!--
+ <node name="clear">
+ <children>
+ <node name="policy">
+ <properties>
+ <help>Clear policy statistics</help>
+ </properties>
+ <children>
+ <tagNode name="ipv6-route">
+ <properties>
+ <help>Clear policy statistics for chain</help>
+ <completionHelp>
+ <path>policy ipv6-route</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified chain</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Clear policy statistics for a rule</help>
+ <completionHelp>
+ <path>policy ipv6-route ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified rule</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="route">
+ <properties>
+ <help>Clear policy statistics for chain</help>
+ <completionHelp>
+ <path>policy route</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified chain</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Clear policy statistics for a rule</help>
+ <completionHelp>
+ <path>policy route ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="counters">
+ <properties>
+ <help>Clear counters for specified rule</help>
+ </properties>
+ <command>echo "TODO"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+-->
+ <node name="show">
+ <children>
+ <node name="policy">
+ <properties>
+ <help>Show policy information</help>
+ </properties>
+ <children>
+ <node name="route6">
+ <properties>
+ <help>Show IPv6 policy chain</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all --ipv6</command>
+ </node>
+ <tagNode name="route6">
+ <properties>
+ <help>Show IPv6 policy chains</help>
+ <completionHelp>
+ <path>policy route6</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 policy rules</help>
+ <completionHelp>
+ <path>policy route6 ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6 --ipv6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --ipv6</command>
+ </tagNode>
+ <node name="route">
+ <properties>
+ <help>Show IPv4 policy chain</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all</command>
+ </node>
+ <tagNode name="route">
+ <properties>
+ <help>Show IPv4 policy chains</help>
+ <completionHelp>
+ <path>policy route</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 policy rules</help>
+ <completionHelp>
+ <path>policy route ${COMP_WORDS[4]} rule</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-virtual-server.xml.in b/op-mode-definitions/show-virtual-server.xml.in
new file mode 100644
index 000000000..5dbd3c759
--- /dev/null
+++ b/op-mode-definitions/show-virtual-server.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="virtual-server">
+ <properties>
+ <help>Show virtual server information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_virtual_server.py</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/zone-policy.xml.in b/op-mode-definitions/zone-policy.xml.in
new file mode 100644
index 000000000..c4b02bcee
--- /dev/null
+++ b/op-mode-definitions/zone-policy.xml.in
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="zone-policy">
+ <properties>
+ <help>Show zone policy information</help>
+ </properties>
+ <children>
+ <tagNode name="zone">
+ <properties>
+ <help>Show summary of zone policy for a specific zone</help>
+ <completionHelp>
+ <path>zone-policy zone</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/zone_policy.py --action show --name $4</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/zone_policy.py --action show</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index d974a7565..e7f515ea9 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -196,7 +196,7 @@ def is_member(conf, interface, intftype=None):
interface name -> Interface is a member of this interface
False -> interface type cannot have members
"""
- ret_val = None
+ ret_val = {}
intftypes = ['bonding', 'bridge']
if intftype not in intftypes + [None]:
@@ -216,8 +216,8 @@ def is_member(conf, interface, intftype=None):
member = base + [intf, 'member', 'interface', interface]
if conf.exists(member):
tmp = conf.get_config_dict(member, key_mangling=('-', '_'),
- get_first_key=True)
- ret_val = {intf : tmp}
+ get_first_key=True, no_tag_node_value_mangle=True)
+ ret_val.update({intf : tmp})
old_level = conf.set_level(old_level)
return ret_val
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 0e41fbe27..4ad7443d7 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -17,7 +17,9 @@ from enum import IntFlag, auto
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import list_diff
from vyos.util import get_sub_dict, mangle_dict_keys
+from vyos.util import dict_search_args
from vyos.xml import defaults
class ConfigDiffError(Exception):
@@ -134,6 +136,34 @@ class ConfigDiff(object):
self._key_mangling[1])
return config_dict
+ def get_child_nodes_diff_str(self, path=[]):
+ ret = {'add': {}, 'change': {}, 'delete': {}}
+
+ diff = self.get_child_nodes_diff(path,
+ expand_nodes=Diff.ADD | Diff.DELETE | Diff.MERGE | Diff.STABLE,
+ no_defaults=True)
+
+ def parse_dict(diff_dict, diff_type, prefix=[]):
+ for k, v in diff_dict.items():
+ if isinstance(v, dict):
+ parse_dict(v, diff_type, prefix + [k])
+ else:
+ path_str = ' '.join(prefix + [k])
+ if diff_type == 'add' or diff_type == 'delete':
+ if isinstance(v, list):
+ v = ', '.join(v)
+ ret[diff_type][path_str] = v
+ elif diff_type == 'merge':
+ old_value = dict_search_args(diff['stable'], *prefix, k)
+ if old_value and old_value != v:
+ ret['change'][path_str] = [old_value, v]
+
+ parse_dict(diff['merge'], 'merge')
+ parse_dict(diff['add'], 'add')
+ parse_dict(diff['delete'], 'delete')
+
+ return ret
+
def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
"""
Args:
diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py
index a0f6a46b5..510b5b65a 100644
--- a/python/vyos/configsource.py
+++ b/python/vyos/configsource.py
@@ -162,7 +162,7 @@ class ConfigSourceSession(ConfigSource):
if p.returncode != 0:
raise VyOSError()
else:
- return out.decode('ascii', 'ignore')
+ return out.decode()
def set_level(self, path):
"""
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index d8ffaca99..866f24e47 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -15,8 +15,9 @@
import re
import json
-from ctypes import cdll, c_char_p, c_void_p, c_int
+from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER
+LIBPATH = '/usr/lib/libvyosconfig.so.0'
def escape_backslash(string: str) -> str:
"""Escape single backslashes in string that are not in escape sequence"""
@@ -42,7 +43,9 @@ class ConfigTreeError(Exception):
class ConfigTree(object):
- def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'):
+ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
+ if config_string is None and address is None:
+ raise TypeError("ConfigTree() requires one of 'config_string' or 'address'")
self.__config = None
self.__lib = cdll.LoadLibrary(libpath)
@@ -60,7 +63,7 @@ class ConfigTree(object):
self.__to_string.restype = c_char_p
self.__to_commands = self.__lib.to_commands
- self.__to_commands.argtypes = [c_void_p]
+ self.__to_commands.argtypes = [c_void_p, c_char_p]
self.__to_commands.restype = c_char_p
self.__to_json = self.__lib.to_json
@@ -126,15 +129,19 @@ class ConfigTree(object):
self.__destroy = self.__lib.destroy
self.__destroy.argtypes = [c_void_p]
- config_section, version_section = extract_version(config_string)
- config_section = escape_backslash(config_section)
- config = self.__from_string(config_section.encode())
- if config is None:
- msg = self.__get_error().decode()
- raise ValueError("Failed to parse config: {0}".format(msg))
+ if address is None:
+ config_section, version_section = extract_version(config_string)
+ config_section = escape_backslash(config_section)
+ config = self.__from_string(config_section.encode())
+ if config is None:
+ msg = self.__get_error().decode()
+ raise ValueError("Failed to parse config: {0}".format(msg))
+ else:
+ self.__config = config
+ self.__version = version_section
else:
- self.__config = config
- self.__version = version_section
+ self.__config = address
+ self.__version = ''
def __del__(self):
if self.__config is not None:
@@ -143,13 +150,16 @@ class ConfigTree(object):
def __str__(self):
return self.to_string()
+ def _get_config(self):
+ return self.__config
+
def to_string(self):
config_string = self.__to_string(self.__config).decode()
config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
- def to_commands(self):
- return self.__to_commands(self.__config).decode()
+ def to_commands(self, op="set"):
+ return self.__to_commands(self.__config, op.encode()).decode()
def to_json(self):
return self.__to_json(self.__config).decode()
@@ -281,3 +291,32 @@ class ConfigTree(object):
else:
raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
+class Diff:
+ def __init__(self, left, right, path=[], libpath=LIBPATH):
+ if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
+ raise TypeError("Arguments must be instances of ConfigTree")
+ if path:
+ if not left.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree")
+ if not right.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree")
+ self.left = left
+ self.right = right
+
+ check_path(path)
+ path_str = " ".join(map(str, path)).encode()
+ df = cdll.LoadLibrary(libpath).diffs
+ df.restype = POINTER(c_void_p * 3)
+ res = list(df(path_str, left._get_config(), right._get_config()).contents)
+ self._diff = {'add': ConfigTree(address=res[0]),
+ 'del': ConfigTree(address=res[1]),
+ 'int': ConfigTree(address=res[2]) }
+
+ self.add = self._diff['add']
+ self.delete = self._diff['del']
+ self.inter = self._diff['int']
+
+ def to_commands(self):
+ add = self.add.to_commands()
+ delete = self.delete.to_commands(op="delete")
+ return delete + "\n" + add
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
new file mode 100644
index 000000000..55ce318e7
--- /dev/null
+++ b/python/vyos/firewall.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 re
+
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def find_nftables_rule(table, chain, rule_matches=[]):
+ # Find rule in table/chain that matches all criteria and return the handle
+ results = cmd(f'sudo nft -a list chain {table} {chain}').split("\n")
+ for line in results:
+ if all(rule_match in line for rule_match in rule_matches):
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ return handle_search[1]
+ return None
+
+def remove_nftables_rule(table, chain, handle):
+ cmd(f'sudo nft delete rule {table} {chain} handle {handle}')
+
+# Functions below used by template generation
+
+def nft_action(vyos_action):
+ if vyos_action == 'accept':
+ return 'return'
+ return vyos_action
+
+def parse_rule(rule_conf, fw_name, rule_id, ip_name):
+ output = []
+ def_suffix = '6' if ip_name == 'ip6' else ''
+
+ if 'state' in rule_conf and rule_conf['state']:
+ states = ",".join([s for s, v in rule_conf['state'].items() if v == 'enable'])
+
+ if states:
+ output.append(f'ct state {{{states}}}')
+
+ if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
+ proto = rule_conf['protocol']
+ operator = ''
+ if proto[0] == '!':
+ operator = '!='
+ proto = proto[1:]
+ if proto == 'tcp_udp':
+ proto = '{tcp, udp}'
+ output.append(f'meta l4proto {operator} {proto}')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ prefix = side[0]
+ side_conf = rule_conf[side]
+
+ if 'address' in side_conf:
+ suffix = side_conf['address']
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'{ip_name} {prefix}addr {suffix}')
+
+ if 'mac_address' in side_conf:
+ suffix = side_conf["mac_address"]
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'ether {prefix}addr {suffix}')
+
+ if 'port' in side_conf:
+ proto = rule_conf['protocol']
+ port = side_conf['port'].split(',')
+
+ ports = []
+ negated_ports = []
+
+ for p in port:
+ if p[0] == '!':
+ negated_ports.append(p[1:])
+ else:
+ ports.append(p)
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ if ports:
+ ports_str = ','.join(ports)
+ output.append(f'{proto} {prefix}port {{{ports_str}}}')
+
+ if negated_ports:
+ negated_ports_str = ','.join(negated_ports)
+ output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}')
+
+ if 'group' in side_conf:
+ group = side_conf['group']
+ if 'address_group' in group:
+ group_name = group['address_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}')
+ elif 'network_group' in group:
+ group_name = group['network_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}')
+ if 'mac_group' in group:
+ group_name = group['mac_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'ether {prefix}addr {operator} $M_{group_name}')
+ if 'port_group' in group:
+ proto = rule_conf['protocol']
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} $P_{group_name}')
+
+ if 'log' in rule_conf and rule_conf['log'] == 'enable':
+ action = rule_conf['action'] if 'action' in rule_conf else 'accept'
+ output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "')
+
+ if 'hop_limit' in rule_conf:
+ operators = {'eq': '==', 'gt': '>', 'lt': '<'}
+ for op, operator in operators.items():
+ if op in rule_conf['hop_limit']:
+ value = rule_conf['hop_limit'][op]
+ output.append(f'ip6 hoplimit {operator} {value}')
+
+ for icmp in ['icmp', 'icmpv6']:
+ if icmp in rule_conf:
+ if 'type_name' in rule_conf[icmp]:
+ output.append(icmp + ' type ' + rule_conf[icmp]['type_name'])
+ else:
+ if 'code' in rule_conf[icmp]:
+ output.append(icmp + ' code ' + rule_conf[icmp]['code'])
+ if 'type' in rule_conf[icmp]:
+ output.append(icmp + ' type ' + rule_conf[icmp]['type'])
+
+ if 'ipsec' in rule_conf:
+ if 'match_ipsec' in rule_conf['ipsec']:
+ output.append('meta ipsec == 1')
+ if 'match_non_ipsec' in rule_conf['ipsec']:
+ output.append('meta ipsec == 0')
+
+ if 'fragment' in rule_conf:
+ # Checking for fragmentation after priority -400 is not possible,
+ # so we use a priority -450 hook to set a mark
+ if 'match_frag' in rule_conf['fragment']:
+ output.append('meta mark 0xffff1')
+ if 'match_non_frag' in rule_conf['fragment']:
+ output.append('meta mark != 0xffff1')
+
+ if 'limit' in rule_conf:
+ if 'rate' in rule_conf['limit']:
+ output.append(f'limit rate {rule_conf["limit"]["rate"]}/second')
+ if 'burst' in rule_conf['limit']:
+ output.append(f'burst {rule_conf["limit"]["burst"]} packets')
+
+ 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 }}')
+
+ if 'time' in rule_conf:
+ output.append(parse_time(rule_conf['time']))
+
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ output.append(parse_tcp_flags(tcp_flags))
+
+ output.append('counter')
+
+ if 'set' in rule_conf:
+ output.append(parse_policy_set(rule_conf['set'], def_suffix))
+
+ if 'action' in rule_conf:
+ output.append(nft_action(rule_conf['action']))
+ else:
+ output.append('return')
+
+ output.append(f'comment "{fw_name}-{rule_id}"')
+ return " ".join(output)
+
+def parse_tcp_flags(flags):
+ include = [flag for flag in flags if flag != 'not']
+ exclude = list(flags['not']) if 'not' in flags else []
+ return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}'
+
+def parse_time(time):
+ out = []
+ if 'startdate' in time:
+ start = time['startdate']
+ if 'T' not in start and 'starttime' in time:
+ start += f' {time["starttime"]}'
+ out.append(f'time >= "{start}"')
+ if 'starttime' in time and 'startdate' not in time:
+ out.append(f'hour >= "{time["starttime"]}"')
+ if 'stopdate' in time:
+ stop = time['stopdate']
+ if 'T' not in stop and 'stoptime' in time:
+ stop += f' {time["stoptime"]}'
+ out.append(f'time < "{stop}"')
+ if 'stoptime' in time and 'stopdate' not in time:
+ out.append(f'hour < "{time["stoptime"]}"')
+ if 'weekdays' in time:
+ days = time['weekdays'].split(",")
+ out_days = [f'"{day}"' for day in days if day[0] != '!']
+ out.append(f'day {{{",".join(out_days)}}}')
+ return " ".join(out)
+
+def parse_policy_set(set_conf, def_suffix):
+ out = []
+ if 'dscp' in set_conf:
+ dscp = set_conf['dscp']
+ out.append(f'ip{def_suffix} dscp set {dscp}')
+ if 'mark' in set_conf:
+ mark = set_conf['mark']
+ out.append(f'meta mark set {mark}')
+ if 'table' in set_conf:
+ table = set_conf['table']
+ if table == 'main':
+ table = '254'
+ mark = 0x7FFFFFFF - int(table)
+ out.append(f'meta mark set {mark}')
+ if 'tcp_mss' in set_conf:
+ mss = set_conf['tcp_mss']
+ out.append(f'tcp option maxseg size set {mss}')
+ return " ".join(out)
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index a8f115d9a..cbba19ab7 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -73,15 +73,15 @@ from vyos.util import cmd
import logging
from logging.handlers import SysLogHandler
import os
+import sys
+
LOG = logging.getLogger(__name__)
+DEBUG = False
-DEBUG = os.path.exists('/tmp/vyos.frr.debug')
-if DEBUG:
- LOG.setLevel(logging.DEBUG)
- ch = SysLogHandler(address='/dev/log')
- ch2 = logging.StreamHandler()
- LOG.addHandler(ch)
- LOG.addHandler(ch2)
+ch = SysLogHandler(address='/dev/log')
+ch2 = logging.StreamHandler(stream=sys.stdout)
+LOG.addHandler(ch)
+LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
@@ -121,6 +121,12 @@ class ConfigSectionNotFound(FrrError):
"""
pass
+def init_debugging():
+ global DEBUG
+
+ DEBUG = os.path.exists('/tmp/vyos.frr.debug')
+ if DEBUG:
+ LOG.setLevel(logging.DEBUG)
def get_configuration(daemon=None, marked=False):
""" Get current running FRR configuration
@@ -424,6 +430,8 @@ class FRRConfig:
Using this overwrites the current loaded config objects and replaces the original loaded config
'''
+ init_debugging()
+
self.imported_config = get_configuration(daemon=daemon)
if daemon:
LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}')
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 2d3e406ac..a37615c8f 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -26,6 +26,7 @@ from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig.geneve import GeneveIf
from vyos.ifconfig.loopback import LoopbackIf
from vyos.ifconfig.macvlan import MACVLANIf
+from vyos.ifconfig.input import InputIf
from vyos.ifconfig.vxlan import VXLANIf
from vyos.ifconfig.wireguard import WireGuardIf
from vyos.ifconfig.vtun import VTunIf
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 5fdd27828..91c7f0c33 100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -577,6 +577,15 @@ class Interface(Control):
return None
return self.set_interface('arp_cache_tmo', tmo)
+ def _cleanup_mss_rules(self, table, ifname):
+ commands = []
+ results = self._cmd(f'nft -a list chain {table} VYOS_TCP_MSS').split("\n")
+ for line in results:
+ if f'oifname "{ifname}"' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ self._cmd(f'nft delete rule {table} VYOS_TCP_MSS handle {handle_search[1]}')
+
def set_tcp_ipv4_mss(self, mss):
"""
Set IPv4 TCP MSS value advertised when TCP SYN packets leave this
@@ -588,22 +597,14 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_ipv4_mss(1340)
"""
- iptables_bin = 'iptables'
- base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = self._cmd(f'{iptables_bin}-save -t mangle')
- for line in out.splitlines():
- if line.startswith(base_options):
- # remove OLD MSS mangling configuration
- line = line.replace('-A FORWARD', '-D FORWARD')
- self._cmd(f'{iptables_bin} -t mangle {line}')
-
- cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ self._cleanup_mss_rules('raw', self.ifname)
+ nft_prefix = 'nft add rule raw VYOS_TCP_MSS'
+ base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
if mss == 'clamp-mss-to-pmtu':
- self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'")
elif int(mss) > 0:
- # probably add option to clamp only if bigger:
low_mss = str(int(mss) + 1)
- self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'")
def set_tcp_ipv6_mss(self, mss):
"""
@@ -616,22 +617,14 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_mss(1320)
"""
- iptables_bin = 'ip6tables'
- base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = self._cmd(f'{iptables_bin}-save -t mangle')
- for line in out.splitlines():
- if line.startswith(base_options):
- # remove OLD MSS mangling configuration
- line = line.replace('-A FORWARD', '-D FORWARD')
- self._cmd(f'{iptables_bin} -t mangle {line}')
-
- cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ self._cleanup_mss_rules('ip6 raw', self.ifname)
+ nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS'
+ base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
if mss == 'clamp-mss-to-pmtu':
- self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'")
elif int(mss) > 0:
- # probably add option to clamp only if bigger:
low_mss = str(int(mss) + 1)
- self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'")
def set_arp_filter(self, arp_filter):
"""
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index aa62ac60d..66044fa52 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -83,8 +83,7 @@ def check_storage(path, size):
directory = path if os.path.isdir(path) else (os.path.dirname(os.path.expanduser(path)) or os.getcwd())
# `size` can be None or 0 to indicate unknown size.
if not size:
- print_error('Warning: Cannot determine size of remote file.')
- print_error('Bravely continuing regardless.')
+ print_error('Warning: Cannot determine size of remote file. Bravely continuing regardless.')
return
if size < 1024 * 1024:
@@ -227,7 +226,7 @@ class HttpC:
r.raise_for_status()
# If the request got redirected, keep the last URL we ended up with.
final_urlstring = r.url
- if r.history:
+ if r.history and self.progressbar:
print_error('Redirecting to ' + final_urlstring)
# Check for the prospective file size.
try:
@@ -317,11 +316,12 @@ def friendly_download(local_path, urlstring, source_host='', source_port=0):
sys.exit(1)
except:
import traceback
+ print_error(f'Failed to download {urlstring}.')
# There are a myriad different reasons a download could fail.
# SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...)
# We omit the scary stack trace but print the error nevertheless.
- print_error(f'Failed to download {urlstring}.')
- traceback.print_exception(*sys.exc_info()[:2], None)
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ traceback.print_exception(exc_type, exc_value, None, 0, None, False)
sys.exit(1)
else:
print_error('Download complete.')
diff --git a/python/vyos/template.py b/python/vyos/template.py
index f694b53e0..dabf53692 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -22,6 +22,7 @@ from jinja2 import FileSystemLoader
from vyos.defaults import directories
from vyos.util import chmod
from vyos.util import chown
+from vyos.util import dict_search_args
from vyos.util import makedir
# Holds template filters registered via register_filter()
@@ -126,6 +127,14 @@ def render(
##################################
# Custom template filters follow #
##################################
+@register_filter('force_to_list')
+def force_to_list(value):
+ """ Convert scalars to single-item lists and leave lists untouched """
+ if isinstance(value, list):
+ return value
+ else:
+ return [value]
+
@register_filter('ip_from_cidr')
def ip_from_cidr(prefix):
""" Take an IPv4/IPv6 CIDR host and strip cidr mask.
@@ -503,3 +512,57 @@ def snmp_auth_oid(type):
'none': '.1.3.6.1.6.3.10.1.2.1'
}
return OIDs[type]
+
+@register_filter('nft_action')
+def nft_action(vyos_action):
+ if vyos_action == 'accept':
+ return 'return'
+ return vyos_action
+
+@register_filter('nft_rule')
+def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
+ from vyos.firewall import parse_rule
+ return parse_rule(rule_conf, fw_name, rule_id, ip_name)
+
+@register_filter('nft_default_rule')
+def nft_default_rule(fw_conf, fw_name):
+ output = ['counter']
+ default_action = fw_conf.get('default_action', 'accept')
+
+ if 'enable_default_log' in fw_conf:
+ 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(f'comment "{fw_name} default-action {default_action}"')
+ return " ".join(output)
+
+@register_filter('nft_state_policy')
+def nft_state_policy(conf, state, ipv6=False):
+ out = [f'ct state {state}']
+
+ if 'log' in conf and 'enable' in conf['log']:
+ out.append('log')
+
+ out.append('counter')
+
+ if 'action' in conf:
+ out.append(conf['action'])
+
+ return " ".join(out)
+
+@register_filter('nft_intra_zone_action')
+def nft_intra_zone_action(zone_conf, ipv6=False):
+ if 'intra_zone_filtering' in zone_conf:
+ intra_zone = zone_conf['intra_zone_filtering']
+ fw_name = 'ipv6_name' if ipv6 else 'name'
+ name_prefix = 'NAME6_' if ipv6 else 'NAME_'
+
+ if 'action' in intra_zone:
+ if intra_zone['action'] == 'accept':
+ return 'return'
+ return intra_zone['action']
+ elif dict_search_args(intra_zone, 'firewall', fw_name):
+ name = dict_search_args(intra_zone, 'firewall', fw_name)
+ return f'jump {name_prefix}{name}'
+ return 'return'
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 954c6670d..571d43754 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -952,14 +952,23 @@ def install_into_config(conf, config_paths, override_prompt=True):
return None
count = 0
+ failed = []
for path in config_paths:
if override_prompt and conf.exists(path) and not conf.is_multi(path):
if not ask_yes_no(f'Config node "{node}" already exists. Do you want to overwrite it?'):
continue
- cmd(f'/opt/vyatta/sbin/my_set {path}')
- count += 1
+ try:
+ cmd(f'/opt/vyatta/sbin/my_set {path}')
+ count += 1
+ except:
+ failed.append(path)
+
+ if failed:
+ print(f'Failed to install {len(failed)} value(s). Commands to manually install:')
+ for path in failed:
+ print(f'set {path}')
if count > 0:
print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.')
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index af7c075e4..63d955738 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -6,6 +6,15 @@ firewall {
ipv6-src-route disable
ip-src-route disable
log-martians enable
+ name test_tcp_flags {
+ rule 1 {
+ action drop
+ protocol tcp
+ tcp {
+ flags SYN,ACK,!RST,!FIN
+ }
+ }
+ }
options {
interface vtun0 {
adjust-mss 1380
@@ -83,6 +92,7 @@ interfaces {
}
policy {
route LAN-POLICY-BASED-ROUTING
+ ipv6-route LAN6-POLICY-BASED-ROUTING
}
smp-affinity auto
speed auto
@@ -383,6 +393,29 @@ nat {
}
}
policy {
+ ipv6-route LAN6-POLICY-BASED-ROUTING {
+ rule 10 {
+ destination {
+ }
+ disable
+ set {
+ table 10
+ }
+ source {
+ address 2002::1
+ }
+ }
+ rule 20 {
+ destination {
+ }
+ set {
+ table 100
+ }
+ source {
+ address 2008::f
+ }
+ }
+ }
prefix-list user2-routes {
rule 1 {
action permit
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index bc0a6c128..9de961249 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -607,11 +607,11 @@ class BasicInterfaceTest:
self.cli_commit()
for interface in self._interfaces:
- base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = cmd('sudo iptables-save -t mangle')
+ base_options = f'oifname "{interface}"'
+ out = cmd('sudo nft list chain raw VYOS_TCP_MSS')
for line in out.splitlines():
if line.startswith(base_options):
- self.assertIn(f'--set-mss {mss}', line)
+ self.assertIn(f'tcp option maxseg size set {mss}', line)
tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms')
self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds
@@ -662,11 +662,11 @@ class BasicInterfaceTest:
self.cli_commit()
for interface in self._interfaces:
- base_options = f'-A FORWARD -o {interface} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = cmd('sudo ip6tables-save -t mangle')
+ base_options = f'oifname "{interface}"'
+ out = cmd('sudo nft list chain ip6 raw VYOS_TCP_MSS')
for line in out.splitlines():
if line.startswith(base_options):
- self.assertIn(f'--set-mss {mss}', line)
+ self.assertIn(f'tcp option maxseg size set {mss}', line)
proc_base = f'/proc/sys/net/ipv6/conf/{interface}'
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index 50f80e7d1..1652aa0d6 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -73,7 +73,7 @@ class VyOSUnitTestSHIM:
def cli_commit(self):
self._session.commit()
# during a commit there is a process opening commit_lock, and run() returns 0
- while run(f'sudo lsof | grep -q {commit_lock}') == 0:
+ while run(f'sudo lsof -nP {commit_lock}') == 0:
sleep(0.250)
def getFRRconfig(self, string, end='$', endsection='^!', daemon=''):
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
new file mode 100755
index 000000000..ecc0c29a0
--- /dev/null
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from glob import glob
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import cmd
+
+sysfs_config = {
+ 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'},
+ 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'default': '1', 'test_value': 'enable'},
+ 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route', 'default': '0', 'test_value': 'enable'},
+ 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'},
+ 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'default': '-1', 'test_value': 'enable'},
+ 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians', 'default': '1', 'test_value': 'disable'},
+ 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'},
+ 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects', 'default': '1', 'test_value': 'disable'},
+ 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies', 'default': '1', 'test_value': 'disable'},
+ 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'}
+}
+
+class TestFirewall(VyOSUnitTestSHIM.TestCase):
+ def setUp(self):
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
+
+ def tearDown(self):
+ self.cli_delete(['interfaces', 'ethernet', 'eth0'])
+ self.cli_commit()
+ self.cli_delete(['firewall'])
+ self.cli_commit()
+
+ def test_groups(self):
+ self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05'])
+ self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
+ self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
+ self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
+ 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(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'],
+ ['ether saddr { 00:01:02:03:04:05 }', 'return']
+ ]
+
+ nftables_output = cmd('sudo nft list table ip 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, msg=search)
+
+ def test_basic_rules(self):
+ self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'protocol', 'tcp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump NAME_smoketest'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'],
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'],
+ ['smoketest default-action', 'drop']
+ ]
+
+ nftables_output = cmd('sudo nft list table ip 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, msg=search)
+
+ def test_basic_rules_ipv6(self):
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'source', 'address', '2002::1'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '1', 'destination', 'address', '2002::1:1'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'action', 'reject'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'rule', '2', 'destination', 'port', '8888'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'ipv6-name', 'v6-smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump NAME6_v6-smoketest'],
+ ['saddr 2002::1', 'daddr 2002::1:1', 'return'],
+ ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'reject'],
+ ['smoketest default-action', 'drop']
+ ]
+
+ nftables_output = cmd('sudo nft list table ip6 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, msg=search)
+
+ 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 filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'],
+ 'ip6 filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
+ }
+
+ for table in ['ip filter', 'ip6 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_sysfs(self):
+ for name, conf in sysfs_config.items():
+ paths = glob(conf['sysfs'])
+ for path in paths:
+ 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_commit()
+
+ for name, conf in sysfs_config.items():
+ paths = glob(conf['sysfs'])
+ for path in paths:
+ with open(path, 'r') as f:
+ self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py
new file mode 100755
index 000000000..e3a91283e
--- /dev/null
+++ b/smoketest/scripts/cli/test_ha_virtual_server.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig.vrrp import VRRP
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import read_file
+from vyos.template import inc_ip
+
+PROCESS_NAME = 'keepalived'
+KEEPALIVED_CONF = VRRP.location['config']
+base_path = ['high-availability']
+vrrp_interface = 'eth1'
+
+class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'address'])
+ 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_ha_virtual_server(self):
+ algo = 'least-connection'
+ delay = '10'
+ method = 'nat'
+ persistence_timeout = '600'
+ vip = '203.0.113.111'
+ vport = '2222'
+ rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23']
+ rport = '22'
+ proto = 'tcp'
+ connection_timeout = '30'
+
+ vserver_base = base_path + ['virtual-server']
+
+ self.cli_set(vserver_base + [vip, 'algorithm', algo])
+ self.cli_set(vserver_base + [vip, 'delay-loop', delay])
+ self.cli_set(vserver_base + [vip, 'forward-method', method])
+ self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout])
+ self.cli_set(vserver_base + [vip, 'port', vport])
+ self.cli_set(vserver_base + [vip, 'protocol', proto])
+ for rs in rservers:
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout])
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(KEEPALIVED_CONF)
+
+ self.assertIn(f'delay_loop {delay}', config)
+ self.assertIn(f'lb_algo lc', config)
+ self.assertIn(f'lb_kind {method.upper()}', config)
+ self.assertIn(f'persistence_timeout {persistence_timeout}', config)
+ self.assertIn(f'protocol {proto.upper()}', config)
+ for rs in rservers:
+ self.assertIn(f'real_server {rs} {rport}', config)
+ self.assertIn(f'{proto.upper()}_CHECK', config)
+ self.assertIn(f'connect_timeout {connection_timeout}', config)
+
+ def test_02_ha_virtual_server_and_vrrp(self):
+ algo = 'least-connection'
+ delay = '15'
+ method = 'nat'
+ persistence_timeout = '300'
+ vip = '203.0.113.222'
+ vport = '22322'
+ rservers = ['192.0.2.11', '192.0.2.12']
+ rport = '222'
+ proto = 'tcp'
+ connection_timeout = '23'
+ group = 'VyOS'
+ vrid = '99'
+
+ vrrp_base = base_path + ['vrrp', 'group']
+ vserver_base = base_path + ['virtual-server']
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'address', '203.0.113.10/24'])
+
+ # VRRP config
+ self.cli_set(vrrp_base + [group, 'description', group])
+ self.cli_set(vrrp_base + [group, 'interface', vrrp_interface])
+ self.cli_set(vrrp_base + [group, 'address', vip + '/24'])
+ self.cli_set(vrrp_base + [group, 'vrid', vrid])
+
+ # Virtual-server config
+ self.cli_set(vserver_base + [vip, 'algorithm', algo])
+ self.cli_set(vserver_base + [vip, 'delay-loop', delay])
+ self.cli_set(vserver_base + [vip, 'forward-method', method])
+ self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout])
+ self.cli_set(vserver_base + [vip, 'port', vport])
+ self.cli_set(vserver_base + [vip, 'protocol', proto])
+ for rs in rservers:
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout])
+ self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(KEEPALIVED_CONF)
+
+ # Keepalived vrrp
+ self.assertIn(f'# {group}', config)
+ self.assertIn(f'interface {vrrp_interface}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'priority 100', config) # default value
+ self.assertIn(f'advert_int 1', config) # default value
+ self.assertIn(f'preempt_delay 0', config) # default value
+
+ # Keepalived virtual-server
+ self.assertIn(f'delay_loop {delay}', config)
+ self.assertIn(f'lb_algo lc', config)
+ self.assertIn(f'lb_kind {method.upper()}', config)
+ self.assertIn(f'persistence_timeout {persistence_timeout}', config)
+ self.assertIn(f'protocol {proto.upper()}', config)
+ for rs in rservers:
+ self.assertIn(f'real_server {rs} {rport}', config)
+ self.assertIn(f'{proto.upper()}_CHECK', config)
+ self.assertIn(f'connect_timeout {connection_timeout}', config)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py
index 23a9f7796..68905e447 100755
--- a/smoketest/scripts/cli/test_ha_vrrp.py
+++ b/smoketest/scripts/cli/test_ha_vrrp.py
@@ -27,7 +27,7 @@ from vyos.template import inc_ip
PROCESS_NAME = 'keepalived'
KEEPALIVED_CONF = VRRP.location['config']
-base_path = ['high-availability', 'vrrp']
+base_path = ['high-availability']
vrrp_interface = 'eth1'
groups = ['VLAN77', 'VLAN78', 'VLAN201']
@@ -56,7 +56,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -91,7 +91,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -138,7 +138,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
vlan_id = group.lstrip('VLAN')
vip = f'100.64.{vlan_id}.1/24'
- group_base = base_path + ['group', group]
+ group_base = base_path + ['vrrp', 'group', group]
self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]])
@@ -146,7 +146,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.cli_set(group_base + ['address', vip])
self.cli_set(group_base + ['vrid', vlan_id])
- self.cli_set(base_path + ['sync-group', sync_group, 'member', group])
+ self.cli_set(base_path + ['vrrp', 'sync-group', sync_group, 'member', group])
# commit changes
self.cli_commit()
@@ -166,5 +166,35 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
self.assertIn(f'{group}', config)
+ def test_04_exclude_vrrp_interface(self):
+ group = 'VyOS-WAN'
+ none_vrrp_interface = 'eth2'
+ vlan_id = '24'
+ vip = '100.64.24.1/24'
+ vip_dev = '192.0.2.2/24'
+ vrid = '150'
+ group_base = base_path + ['vrrp', 'group', group]
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24'])
+ self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}'])
+ self.cli_set(group_base + ['address', vip])
+ self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface])
+ self.cli_set(group_base + ['track', 'exclude-vrrp-interface'])
+ self.cli_set(group_base + ['track', 'interface', none_vrrp_interface])
+ self.cli_set(group_base + ['vrid', vrid])
+
+ # commit changes
+ self.cli_commit()
+
+ config = getConfig(f'vrrp_instance {group}')
+
+ self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'dont_track_primary', config)
+ self.assertIn(f' {vip}', config)
+ self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config)
+ self.assertIn(f'track_interface', config)
+ self.assertIn(f' {none_vrrp_interface}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 86000553e..1d9a887bd 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -36,7 +36,6 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
cls._test_vlan = True
cls._test_qinq = True
cls._base_path = ['interfaces', 'bonding']
- cls._interfaces = ['bond0']
cls._mirror_interfaces = ['dum21354']
cls._members = []
@@ -52,6 +51,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
cls._options['bond0'] = []
for member in cls._members:
cls._options['bond0'].append(f'member interface {member}')
+ cls._interfaces = list(cls._options)
# call base-classes classmethod
super(cls, cls).setUpClass()
@@ -150,5 +150,19 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
defined_policy = read_file(f'/sys/class/net/{interface}/bonding/xmit_hash_policy').split()
self.assertEqual(defined_policy[0], hash_policy)
+ def test_bonding_multi_use_member(self):
+ # Define available bonding hash policies
+ for interface in ['bond10', 'bond20']:
+ for member in self._members:
+ self.cli_set(self._base_path + [interface, 'member', 'interface', member])
+
+ # check validate() - can not use the same member interfaces multiple times
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(self._base_path + ['bond20'])
+
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index 5844e1ec1..491f1766d 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1135,18 +1135,13 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- # Check generated configuration
-
- # Expected values
original = """
50: from 203.0.113.1 lookup 23
50: from 203.0.113.2 lookup 23
"""
tmp = cmd('ip rule show prio 50')
- original = original.split()
- tmp = tmp.split()
- self.assertEqual(tmp, original)
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
# Test set table for fwmark
def test_fwmark_table_id(self):
@@ -1161,17 +1156,32 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- # Check generated configuration
-
- # Expected values
original = """
101: from all fwmark 0x18 lookup 154
"""
tmp = cmd('ip rule show prio 101')
- original = original.split()
- tmp = tmp.split()
- self.assertEqual(tmp, original)
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for destination
+ def test_destination_table_id(self):
+ path = base_path + ['local-route']
+
+ dst = '203.0.113.1'
+ rule = '102'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+
+ self.cli_commit()
+
+ original = """
+ 102: from all to 203.0.113.1 lookup 154
+ """
+ tmp = cmd('ip rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
# Test set table for sources with fwmark
def test_fwmark_sources_table_id(self):
@@ -1188,18 +1198,301 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- # Check generated configuration
-
- # Expected values
original = """
100: from 203.0.113.11 fwmark 0x17 lookup 150
100: from 203.0.113.12 fwmark 0x17 lookup 150
"""
tmp = cmd('ip rule show prio 100')
- original = original.split()
- tmp = tmp.split()
- self.assertEqual(tmp, original)
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with iif
+ def test_iif_sources_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ iif = 'lo'
+ rule = '100'
+ table = '150'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ # Check generated configuration
+ # Expected values
+ original = """
+ 100: from 203.0.113.11 iif lo lookup 150
+ 100: from 203.0.113.12 iif lo lookup 150
+ """
+ tmp = cmd('ip rule show prio 100')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources and destinations with fwmark
+ def test_fwmark_sources_destination_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['203.0.113.11', '203.0.113.12']
+ destinations = ['203.0.113.13', '203.0.113.15']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150
+ 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150
+ 103: from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150
+ 103: from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table ipv6 for some sources ipv6
+ def test_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:123::/48', '2001:db8:126::/48']
+ rule = '50'
+ table = '23'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ original = """
+ 50: from 2001:db8:123::/48 lookup 23
+ 50: from 2001:db8:126::/48 lookup 23
+ """
+ tmp = cmd('ip -6 rule show prio 50')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for fwmark ipv6
+ def test_fwmark_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ fwmk = '24'
+ rule = '100'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 100: from all fwmark 0x18 lookup 154
+ """
+ tmp = cmd('ip -6 rule show prio 100')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for destination ipv6
+ def test_destination_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ dst = '2001:db8:1337::/126'
+ rule = '101'
+ table = '154'
+
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+
+ self.cli_commit()
+
+ original = """
+ 101: from all to 2001:db8:1337::/126 lookup 154
+ """
+ tmp = cmd('ip -6 rule show prio 101')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with fwmark ipv6
+ def test_fwmark_sources_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+ fwmk = '23'
+ rule = '102'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150
+ 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources with iif ipv6
+ def test_iif_sources_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/126']
+ iif = 'lo'
+ rule = '102'
+ table = '150'
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'inbound-interface', iif])
+
+ self.cli_commit()
+
+ # Check generated configuration
+ # Expected values
+ original = """
+ 102: from 2001:db8:1338::/126 iif lo lookup 150
+ 102: from 2001:db8:1339::/126 iif lo lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 102')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test set table for sources and destinations with fwmark ipv6
+ def test_fwmark_sources_destination_ipv6_table_id(self):
+ path = base_path + ['local-route6']
+
+ sources = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+ destinations = ['2001:db8:13::/48', '2001:db8:16::/48']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+
+ # Test delete table for sources and destination with fwmark ipv4/ipv6
+ def test_delete_ipv4_ipv6_table_id(self):
+ path = base_path + ['local-route']
+ path_v6 = base_path + ['local-route6']
+
+ sources = ['203.0.113.0/24', '203.0.114.5']
+ destinations = ['203.0.112.0/24', '203.0.116.5']
+ sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56']
+ destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48']
+ fwmk = '23'
+ rule = '103'
+ table = '150'
+ for src in sources:
+ for dst in destinations:
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path + ['rule', rule, 'source', src])
+ self.cli_set(path + ['rule', rule, 'destination', dst])
+ self.cli_set(path + ['rule', rule, 'fwmark', fwmk])
+
+ for src in sources_v6:
+ for dst in destinations_v6:
+ self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table])
+ self.cli_set(path_v6 + ['rule', rule, 'source', src])
+ self.cli_set(path_v6 + ['rule', rule, 'destination', dst])
+ self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk])
+
+ self.cli_commit()
+
+ original = """
+ 103: from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150
+ 103: from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150
+ 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150
+ 103: from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150
+ """
+ original_v6 = """
+ 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150
+ 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150
+ """
+ tmp = cmd('ip rule show prio 103')
+ tmp_v6 = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original))
+ self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6))
+
+ self.cli_delete(path)
+ self.cli_delete(path_v6)
+ self.cli_commit()
+
+ tmp = cmd('ip rule show prio 103')
+ tmp_v6 = cmd('ip -6 rule show prio 103')
+
+ self.assertEqual(sort_ip(tmp), [])
+ self.assertEqual(sort_ip(tmp_v6), [])
+
+ # Test multiple commits ipv4
+ def test_multiple_commit_ipv4_table_id(self):
+ path = base_path + ['local-route']
+
+ sources = ['192.0.2.1', '192.0.2.2']
+ destination = '203.0.113.25'
+ rule = '105'
+ table = '151'
+ self.cli_set(path + ['rule', rule, 'set', 'table', table])
+ for src in sources:
+ self.cli_set(path + ['rule', rule, 'source', src])
+
+ self.cli_commit()
+
+ original_first = """
+ 105: from 192.0.2.1 lookup 151
+ 105: from 192.0.2.2 lookup 151
+ """
+ tmp = cmd('ip rule show prio 105')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original_first))
+
+ # Create second commit with added destination
+ self.cli_set(path + ['rule', rule, 'destination', destination])
+ self.cli_commit()
+
+ original_second = """
+ 105: from 192.0.2.1 to 203.0.113.25 lookup 151
+ 105: from 192.0.2.2 to 203.0.113.25 lookup 151
+ """
+ tmp = cmd('ip rule show prio 105')
+
+ self.assertEqual(sort_ip(tmp), sort_ip(original_second))
+
+
+def sort_ip(output):
+ o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()])
+ o = o.splitlines()
+ o.sort()
+ return o
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
new file mode 100755
index 000000000..9035f0832
--- /dev/null
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import cmd
+
+mark = '100'
+table_mark_offset = 0x7fffffff
+table_id = '101'
+
+class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
+ def setUp(self):
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24'])
+ self.cli_set(['protocols', 'static', 'table', '101', 'route', '0.0.0.0/0', 'interface', 'eth0'])
+
+ def tearDown(self):
+ self.cli_delete(['interfaces', 'ethernet', 'eth0'])
+ self.cli_delete(['protocols', 'static'])
+ self.cli_delete(['policy', 'route'])
+ self.cli_delete(['policy', 'route6'])
+ self.cli_commit()
+
+ def test_pbr_mark(self):
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
+
+ self.cli_commit()
+
+ mark_hex = "{0:#010x}".format(int(mark))
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
+ ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex],
+ ]
+
+ nftables_output = cmd('sudo nft list table ip mangle')
+
+ 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)
+
+ def test_pbr_table(self):
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route6', 'smoketest6'])
+
+ self.cli_commit()
+
+ mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id))
+
+ # IPv4
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex]
+ ]
+
+ nftables_output = cmd('sudo nft list table ip mangle')
+
+ 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)
+
+ # IPv6
+
+ nftables6_search = [
+ ['iifname "eth0"', 'jump VYOS_PBR6_smoketest'],
+ ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
+ ]
+
+ nftables6_output = cmd('sudo nft list table ip6 mangle')
+
+ for search in nftables6_search:
+ matched = False
+ for line in nftables6_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(matched)
+
+ # IP rule fwmark -> table
+
+ ip_rule_search = [
+ ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id]
+ ]
+
+ ip_rule_output = cmd('ip rule show')
+
+ for search in ip_rule_search:
+ matched = False
+ for line in ip_rule_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_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py
index aa0ac268d..40b19fec7 100755
--- a/smoketest/scripts/cli/test_protocols_nhrp.py
+++ b/smoketest/scripts/cli/test_protocols_nhrp.py
@@ -18,6 +18,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.firewall import find_nftables_rule
from vyos.util import call, process_named_running, read_file
tunnel_path = ['interfaces', 'tunnel']
@@ -91,6 +92,14 @@ class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):
for line in opennhrp_lines:
self.assertIn(line, tmp_opennhrp_conf)
+ firewall_matches = [
+ 'ip protocol gre',
+ 'ip saddr 192.0.2.1',
+ 'ip daddr 224.0.0.0/4',
+ 'comment "VYOS_NHRP_tun100"'
+ ]
+
+ self.assertTrue(find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', firewall_matches) is not None)
self.assertTrue(process_named_running('opennhrp'))
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
new file mode 100755
index 000000000..b857926e2
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
+from vyos.util import process_named_running
+from vyos.util import read_file
+
+PROCESS_NAME = 'telegraf'
+TELEGRAF_CONF = '/run/telegraf/vyos-telegraf.conf'
+base_path = ['service', 'monitoring', 'telegraf']
+org = 'log@in.local'
+token = 'GuRJc12tIzfjnYdKRAIYbxdWd2aTpOT9PVYNddzDnFV4HkAcD7u7-kndTFXjGuXzJN6TTxmrvPODB4mnFcseDV=='
+port = '8888'
+url = 'https://foo.local'
+bucket = 'main'
+inputs = ['cpu', 'disk', 'mem', 'net', 'system', 'kernel', 'interrupts', 'syslog']
+
+class TestMonitoringTelegraf(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_01_basic_config(self):
+ self.cli_set(base_path + ['authentication', 'organization', org])
+ self.cli_set(base_path + ['authentication', 'token', token])
+ self.cli_set(base_path + ['port', port])
+ self.cli_set(base_path + ['url', url])
+
+ # commit changes
+ self.cli_commit()
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ config = read_file(TELEGRAF_CONF)
+
+ # Check telegraf config
+ self.assertIn(f'organization = "{org}"', config)
+ self.assertIn(token, config)
+ self.assertIn(f'urls = ["{url}:{port}"]', config)
+ self.assertIn(f'bucket = "{bucket}"', config)
+
+ for input in inputs:
+ self.assertIn(input, config)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py
new file mode 100755
index 000000000..c3e9b600f
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_upnp.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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 re
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
+from vyos.template import ip_from_cidr
+from vyos.util import read_file
+from vyos.util import process_named_running
+
+UPNP_CONF = '/run/upnp/miniupnp.conf'
+DAEMON = 'miniupnpd'
+interface = 'eth0'
+base_path = ['service', 'upnp']
+address_base = ['interfaces', 'ethernet', interface, 'address']
+
+ipv4_addr = '100.64.0.1/24'
+ipv6_addr = '2001:db8::1/64'
+
+class TestServiceUPnP(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ cls.cli_set(cls, address_base + [ipv4_addr])
+ cls.cli_set(cls, address_base + [ipv6_addr])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, address_base)
+ cls._session.commit()
+
+ super(cls, cls).tearDownClass()
+
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(DAEMON))
+
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Check for running process
+ self.assertFalse(process_named_running(DAEMON))
+
+ def test_ipv4_base(self):
+ self.cli_set(base_path + ['nat-pmp'])
+ self.cli_set(base_path + ['listen', interface])
+
+ # check validate() - WAN interface is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['wan-interface', interface])
+
+ self.cli_commit()
+
+ config = read_file(UPNP_CONF)
+ self.assertIn(f'ext_ifname={interface}', config)
+ self.assertIn(f'listening_ip={interface}', config)
+ self.assertIn(f'enable_natpmp=yes', config)
+ self.assertIn(f'enable_upnp=yes', config)
+
+ def test_ipv6_base(self):
+ v6_addr = ip_from_cidr(ipv6_addr)
+
+ self.cli_set(base_path + ['nat-pmp'])
+ self.cli_set(base_path + ['listen', interface])
+ self.cli_set(base_path + ['listen', v6_addr])
+
+ # check validate() - WAN interface is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['wan-interface', interface])
+
+ self.cli_commit()
+
+ config = read_file(UPNP_CONF)
+ self.assertIn(f'ext_ifname={interface}', config)
+ self.assertIn(f'listening_ip={interface}', config)
+ self.assertIn(f'ipv6_listening_ip={v6_addr}', config)
+ self.assertIn(f'enable_natpmp=yes', config)
+ self.assertIn(f'enable_upnp=yes', config)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index b2934cf04..95c2a6c55 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -15,10 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.firewall import find_nftables_rule
from vyos.util import cmd
from vyos.util import read_file
@@ -156,8 +158,8 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
'driver' : ['nf_nat_h323', 'nf_conntrack_h323'],
},
'nfs' : {
- 'iptables' : ['-A VYATTA_CT_HELPER -p udp -m udp --dport 111 -j CT --helper rpc',
- '-A VYATTA_CT_HELPER -p tcp -m tcp --dport 111 -j CT --helper rpc'],
+ 'nftables' : ['ct helper set "rpc_tcp"',
+ 'ct helper set "rpc_udp"']
},
'pptp' : {
'driver' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
@@ -166,9 +168,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
'driver' : ['nf_nat_sip', 'nf_conntrack_sip'],
},
'sqlnet' : {
- 'iptables' : ['-A VYATTA_CT_HELPER -p tcp -m tcp --dport 1536 -j CT --helper tns',
- '-A VYATTA_CT_HELPER -p tcp -m tcp --dport 1525 -j CT --helper tns',
- '-A VYATTA_CT_HELPER -p tcp -m tcp --dport 1521 -j CT --helper tns'],
+ 'nftables' : ['ct helper set "tns_tcp"']
},
'tftp' : {
'driver' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
@@ -187,10 +187,9 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
if 'driver' in module_options:
for driver in module_options['driver']:
self.assertTrue(os.path.isdir(f'/sys/module/{driver}'))
- if 'iptables' in module_options:
- rules = cmd('sudo iptables-save -t raw')
- for ruleset in module_options['iptables']:
- self.assertIn(ruleset, rules)
+ if 'nftables' in module_options:
+ for rule in module_options['nftables']:
+ self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) != None)
# unload modules
for module in modules:
@@ -204,10 +203,9 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
if 'driver' in module_options:
for driver in module_options['driver']:
self.assertFalse(os.path.isdir(f'/sys/module/{driver}'))
- if 'iptables' in module_options:
- rules = cmd('sudo iptables-save -t raw')
- for ruleset in module_options['iptables']:
- self.assertNotIn(ruleset, rules)
+ if 'nftables' in module_options:
+ for rule in module_options['nftables']:
+ self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) == None)
def test_conntrack_hash_size(self):
hash_size = '65536'
diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py
index a53999461..857df1be6 100755
--- a/smoketest/scripts/cli/test_system_flow-accounting.py
+++ b/smoketest/scripts/cli/test_system_flow-accounting.py
@@ -62,9 +62,20 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# verify configuration
- tmp = cmd('sudo iptables-save -t raw')
+ nftables_output = cmd('sudo nft list chain raw VYOS_CT_PREROUTING_HOOK').splitlines()
for interface in Section.interfaces('ethernet'):
- self.assertIn(f'-A VYATTA_CT_PREROUTING_HOOK -i {interface} -m comment --comment FLOW_ACCOUNTING_RULE -j NFLOG --nflog-group 2 --nflog-size 128 --nflog-threshold 100', tmp)
+ rule_found = False
+ ifname_search = f'iifname "{interface}"'
+
+ for nftables_line in nftables_output:
+ if 'FLOW_ACCOUNTING_RULE' in nftables_line and ifname_search in nftables_line:
+ self.assertIn('group 2', nftables_line)
+ self.assertIn('snaplen 128', nftables_line)
+ self.assertIn('queue-threshold 100', nftables_line)
+ rule_found = True
+ break
+
+ self.assertTrue(rule_found)
uacctd = read_file(uacctd_conf)
# circular queue size - buffer_size
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index e8cc64463..c8cf04b7d 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import re
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -29,17 +28,14 @@ PROCESS_NAME = 'ntpd'
NTP_CONF = '/run/ntpd/ntpd.conf'
base_path = ['system', 'ntp']
-def get_config_value(key):
- tmp = read_file(NTP_CONF)
- tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp)
- # remove possible trailing whitespaces
- return [item.strip() for item in tmp]
-
class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
- self.cli_delete(base_path)
+ cls.cli_delete(cls, base_path)
def tearDown(self):
self.cli_delete(base_path)
@@ -47,35 +43,38 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
self.assertFalse(process_named_running(PROCESS_NAME))
- def test_ntp_options(self):
+ def test_01_ntp_options(self):
# Test basic NTP support with multiple servers and their options
servers = ['192.0.2.1', '192.0.2.2']
options = ['noselect', 'preempt', 'prefer']
- ntp_pool = 'pool.vyos.io'
+ pools = ['pool.vyos.io']
for server in servers:
for option in options:
self.cli_set(base_path + ['server', server, option])
# Test NTP pool
- self.cli_set(base_path + ['server', ntp_pool, 'pool'])
+ for pool in pools:
+ self.cli_set(base_path + ['server', pool, 'pool'])
# commit changes
self.cli_commit()
# Check generated configuration
- tmp = get_config_value('server')
- for server in servers:
- test = f'{server} iburst ' + ' '.join(options)
- self.assertTrue(test in tmp)
+ config = read_file(NTP_CONF)
+ self.assertIn('driftfile /var/lib/ntp/ntp.drift', config)
+ self.assertIn('restrict default noquery nopeer notrap nomodify', config)
+ self.assertIn('restrict source nomodify notrap noquery', config)
+ self.assertIn('restrict 127.0.0.1', config)
+ self.assertIn('restrict -6 ::1', config)
- tmp = get_config_value('pool')
- self.assertTrue(f'{ntp_pool} iburst' in tmp)
+ for server in servers:
+ self.assertIn(f'server {server} iburst ' + ' '.join(options), config)
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ for pool in pools:
+ self.assertIn(f'pool {pool} iburst', config)
- def test_ntp_clients(self):
+ def test_02_ntp_clients(self):
# Test the allowed-networks statement
listen_address = ['127.0.0.1', '::1']
for listen in listen_address:
@@ -96,23 +95,18 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Check generated client address configuration
+ config = read_file(NTP_CONF)
+ self.assertIn('restrict default ignore', config)
+
for network in networks:
network_address = address_from_cidr(network)
network_netmask = netmask_from_cidr(network)
-
- tmp = get_config_value(f'restrict {network_address}')[0]
- test = f'mask {network_netmask} nomodify notrap nopeer'
- self.assertTrue(tmp in test)
+ self.assertIn(f'restrict {network_address} mask {network_netmask} nomodify notrap nopeer', config)
# Check listen address
- tmp = get_config_value('interface')
- test = ['ignore wildcard']
+ self.assertIn('interface ignore wildcard', config)
for listen in listen_address:
- test.append(f'listen {listen}')
- self.assertEqual(tmp, test)
-
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertIn(f'interface listen {listen}', config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index c710aec6e..1433c7329 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -111,9 +111,22 @@ rgiyCHemtMepq57Pl1Nmj49eEA==
"""
class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(base_path + ['interface', f'{interface}.{vif}'])
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ cls.cli_set(cls, base_path + ['interface', f'{interface}.{vif}'])
+
+ @classmethod
+ def tearDownClass(cls):
+ super(cls, cls).tearDownClass()
+
+ cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}'])
+ def setUp(self):
# Set IKE/ESP Groups
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes128'])
self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha1'])
@@ -127,7 +140,6 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_delete(tunnel_path)
- self.cli_delete(ethernet_path)
self.cli_commit()
# Check for no longer running process
@@ -158,6 +170,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
# Site to site
local_address = '192.0.2.10'
+ priority = '20'
peer_base_path = base_path + ['site-to-site', 'peer', peer_ip]
self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])
@@ -173,6 +186,10 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24'])
self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'port', '443'])
+ self.cli_set(peer_base_path + ['tunnel', '2', 'local', 'prefix', '10.1.0.0/16'])
+ self.cli_set(peer_base_path + ['tunnel', '2', 'remote', 'prefix', '10.2.0.0/16'])
+ self.cli_set(peer_base_path + ['tunnel', '2', 'priority', priority])
+
self.cli_commit()
# Verify strongSwan configuration
@@ -187,8 +204,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
f'local_addrs = {local_address} # dhcp:no',
f'remote_addrs = {peer_ip}',
f'mode = tunnel',
+ f'peer_{peer_ip.replace(".","-")}_tunnel_1',
f'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]',
- f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]'
+ f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]',
+ f'mode = tunnel',
+ f'peer_{peer_ip.replace(".","-")}_tunnel_2',
+ f'local_ts = 10.1.0.0/16',
+ f'remote_ts = 10.2.0.0/16',
+ f'priority = {priority}',
+ f'mode = tunnel',
]
for line in swanctl_conf_lines:
self.assertIn(line, swanctl_conf)
diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py
new file mode 100755
index 000000000..00dfe0182
--- /dev/null
+++ b/smoketest/scripts/cli/test_zone_policy.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.util import cmd
+
+class TestZonePolicy(VyOSUnitTestSHIM.TestCase):
+ def setUp(self):
+ self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
+
+ def tearDown(self):
+ self.cli_delete(['zone-policy'])
+ self.cli_delete(['firewall'])
+ self.cli_commit()
+
+ def test_basic_zone(self):
+ self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
+ self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
+ self.cli_set(['zone-policy', 'zone', 'smoketest-local', 'local-zone'])
+ self.cli_set(['zone-policy', '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 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/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 68877f794..aabf2bdf5 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -15,11 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+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.util import cmd
from vyos.util import run
from vyos.util import process_named_running
@@ -32,6 +35,7 @@ airbag.enable()
conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf'
sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf'
+nftables_ct_file = r'/run/nftables-ct.conf'
# Every ALG (Application Layer Gateway) consists of either a Kernel Object
# also called a Kernel Module/Driver or some rules present in iptables
@@ -43,8 +47,8 @@ module_map = {
'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
},
'nfs' : {
- 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 111 --jump CT --helper rpc',
- 'VYATTA_CT_HELPER --table raw --proto udp --dport 111 --jump CT --helper rpc'],
+ 'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return',
+ 'ct helper set "rpc_udp" udp dport "{111}" return']
},
'pptp' : {
'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
@@ -53,9 +57,7 @@ module_map = {
'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
},
'sqlnet' : {
- 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 1521 --jump CT --helper tns',
- 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1525 --jump CT --helper tns',
- 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1536 --jump CT --helper tns'],
+ 'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return']
},
'tftp' : {
'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
@@ -80,19 +82,49 @@ 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)
+ # 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)
return conntrack
def verify(conntrack):
+ if dict_search('ignore.rule', conntrack) != None:
+ for rule, rule_config in conntrack['ignore']['rule'].items():
+ if dict_search('destination.port', rule_config) or \
+ dict_search('source.port', rule_config):
+ if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
+ raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
+
return None
def generate(conntrack):
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack)
+ render(nftables_ct_file, 'conntrack/nftables-ct.tmpl', conntrack)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft -c -f {nftables_ct_file}')
+ if tmp > 0:
+ if os.path.exists(nftables_ct_file):
+ os.unlink(nftables_ct_file)
+ raise ConfigError('Configuration file errors encountered!')
return None
+def find_nftables_ct_rule(rule):
+ helper_search = re.search('ct helper set "(\w+)"', rule)
+ if helper_search:
+ rule = helper_search[1]
+ return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule])
+
+def find_remove_rule(rule):
+ handle = find_nftables_ct_rule(rule)
+ if handle:
+ remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle)
+
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
# modules we need to either insmod or rmmod the helpers.
@@ -103,20 +135,20 @@ def apply(conntrack):
# Only remove the module if it's loaded
if os.path.exists(f'/sys/module/{mod}'):
cmd(f'rmmod {mod}')
- if 'iptables' in module_config:
- for rule in module_config['iptables']:
- # Only install iptables rule if it does not exist
- tmp = run(f'iptables --check {rule}')
- if tmp == 0: cmd(f'iptables --delete {rule}')
+ if 'nftables' in module_config:
+ for rule in module_config['nftables']:
+ find_remove_rule(rule)
else:
if 'ko' in module_config:
for mod in module_config['ko']:
cmd(f'modprobe {mod}')
- if 'iptables' in module_config:
- for rule in module_config['iptables']:
- # Only install iptables rule if it does not exist
- tmp = run(f'iptables --check {rule}')
- if tmp > 0: cmd(f'iptables --insert {rule}')
+ if 'nftables' in module_config:
+ for rule in module_config['nftables']:
+ if not find_nftables_ct_rule(rule):
+ cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
+
+ # Load new nftables ruleset
+ cmd(f'nft -f {nftables_ct_file}')
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py
index f82a077e6..8f9837c2b 100755
--- a/src/conf_mode/conntrack_sync.py
+++ b/src/conf_mode/conntrack_sync.py
@@ -36,7 +36,7 @@ airbag.enable()
config_file = '/run/conntrackd/conntrackd.conf'
def resync_vrrp():
- tmp = run('/usr/libexec/vyos/conf_mode/vrrp.py')
+ tmp = run('/usr/libexec/vyos/conf_mode/high-availability.py')
if tmp > 0:
print('ERROR: error restarting VRRP daemon!')
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py
index 2e14e0b25..26c50cab6 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/containers.py
@@ -298,7 +298,7 @@ def apply(container):
f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {port} {volume} {env_opt}'
if 'allow_host_networks' in container_config:
- _cmd(f'{container_base_cmd} --net host {image}')
+ run(f'{container_base_cmd} --net host {image}')
else:
for network in container_config['network']:
ipparam = ''
@@ -306,19 +306,25 @@ def apply(container):
address = container_config['network'][network]['address']
ipparam = f'--ip {address}'
- counter = 0
- while True:
- if counter >= 10:
- break
- try:
- _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}')
- break
- except:
- counter = counter +1
- sleep(0.5)
+ run(f'{container_base_cmd} --net {network} {ipparam} {image}')
return None
+def run(container_cmd):
+ counter = 0
+ while True:
+ if counter >= 10:
+ break
+ try:
+ _cmd(container_cmd)
+ break
+ except:
+ counter = counter +1
+ sleep(0.5)
+
+ return None
+
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
new file mode 100755
index 000000000..9a5d278e9
--- /dev/null
+++ b/src/conf_mode/firewall-interface.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import leaf_node_changed
+from vyos.ifconfig import Section
+from vyos.template import render
+from vyos.util import cmd
+from vyos.util import dict_search_args
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+NAME_PREFIX = 'NAME_'
+NAME6_PREFIX = 'NAME6_'
+
+NFT_CHAINS = {
+ 'in': 'VYOS_FW_FORWARD',
+ 'out': 'VYOS_FW_FORWARD',
+ 'local': 'VYOS_FW_LOCAL'
+}
+NFT6_CHAINS = {
+ 'in': 'VYOS_FW6_FORWARD',
+ 'out': 'VYOS_FW6_FORWARD',
+ 'local': 'VYOS_FW6_LOCAL'
+}
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ ifname = argv[1]
+ ifpath = Section.get_config_path(ifname)
+ if_firewall_path = f'interfaces {ifpath} firewall'
+
+ if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if_firewall['ifname'] = ifname
+ if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return if_firewall
+
+def verify(if_firewall):
+ # bail out early - looks like removal from running config
+ if not if_firewall:
+ return None
+
+ for direction in ['in', 'out', 'local']:
+ if direction in if_firewall:
+ if 'name' in if_firewall[direction]:
+ name = if_firewall[direction]['name']
+
+ if 'name' not in if_firewall['firewall']:
+ raise ConfigError('Firewall name not configured')
+
+ if name not in if_firewall['firewall']['name']:
+ raise ConfigError(f'Invalid firewall name "{name}"')
+
+ if 'ipv6_name' in if_firewall[direction]:
+ name = if_firewall[direction]['ipv6_name']
+
+ if 'ipv6_name' not in if_firewall['firewall']:
+ raise ConfigError('Firewall ipv6-name not configured')
+
+ if name not in if_firewall['firewall']['ipv6_name']:
+ raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
+
+ return None
+
+def generate(if_firewall):
+ return None
+
+def cleanup_rule(table, chain, prefix, ifname, new_name=None):
+ results = cmd(f'nft -a list chain {table} {chain}').split("\n")
+ retval = None
+ for line in results:
+ if f'{prefix}ifname "{ifname}"' in line:
+ if new_name and f'jump {new_name}' in line:
+ # new_name is used to clear rules for any previously referenced chains
+ # returns true when rule exists and doesn't need to be created
+ retval = True
+ continue
+
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ run(f'nft delete rule {table} {chain} handle {handle_search[1]}')
+ return retval
+
+def state_policy_handle(table, chain):
+ # Find any state-policy rule to ensure interface rules are only inserted afterwards
+ results = cmd(f'nft -a list chain {table} {chain}').split("\n")
+ for line in results:
+ if 'jump VYOS_STATE_POLICY' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ return handle_search[1]
+ return None
+
+def apply(if_firewall):
+ ifname = if_firewall['ifname']
+
+ for direction in ['in', 'out', 'local']:
+ chain = NFT_CHAINS[direction]
+ ipv6_chain = NFT6_CHAINS[direction]
+ if_prefix = 'i' if direction in ['in', 'local'] else 'o'
+
+ name = dict_search_args(if_firewall, direction, 'name')
+ if name:
+ rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}')
+
+ if not rule_exists:
+ rule_action = 'insert'
+ rule_prefix = ''
+
+ handle = state_policy_handle('ip filter', chain)
+ if handle:
+ rule_action = 'add'
+ rule_prefix = f'position {handle}'
+
+ run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}')
+ else:
+ cleanup_rule('ip filter', chain, if_prefix, ifname)
+
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+ if ipv6_name:
+ rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}')
+
+ if not rule_exists:
+ rule_action = 'insert'
+ rule_prefix = ''
+
+ handle = state_policy_handle('ip6 filter', ipv6_chain)
+ if handle:
+ rule_action = 'add'
+ rule_prefix = f'position {handle}'
+
+ run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}')
+ else:
+ cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname)
+
+ return None
+
+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/firewall.py b/src/conf_mode/firewall.py
index 8e6ce5b14..41df1b84a 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -15,51 +15,406 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
+from glob import glob
+from json import loads
from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
-from vyos.configdict import leaf_node_changed
+from vyos.configdiff import get_config_diff, Diff
from vyos.template import render
-from vyos.util import call
+from vyos.util import cmd
+from vyos.util import dict_search_args
+from vyos.util import process_named_running
+from vyos.util import run
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
-from pprint import pprint
airbag.enable()
+policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
-def get_config(config=None):
+nftables_conf = '/run/nftables.conf'
+nftables_defines_conf = '/run/nftables_defines.conf'
+
+sysfs_config = {
+ 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
+ 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'enable': '0', 'disable': '1'},
+ 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route'},
+ 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects'},
+ 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'enable': '0', 'disable': '-1'},
+ 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'},
+ 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'},
+ 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'},
+ 'source_validation': {'sysfs': '/proc/sys/net/ipv4/conf/*/rp_filter', 'disable': '0', 'strict': '1', 'loose': '2'},
+ 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'},
+ 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
+}
+
+NAME_PREFIX = 'NAME_'
+NAME6_PREFIX = 'NAME6_'
+
+preserve_chains = [
+ 'INPUT',
+ 'FORWARD',
+ 'OUTPUT',
+ 'VYOS_FW_FORWARD',
+ 'VYOS_FW_LOCAL',
+ 'VYOS_FW_OUTPUT',
+ 'VYOS_POST_FW',
+ 'VYOS_FRAG_MARK',
+ 'VYOS_FW6_FORWARD',
+ 'VYOS_FW6_LOCAL',
+ 'VYOS_FW6_OUTPUT',
+ 'VYOS_POST_FW6',
+ 'VYOS_FRAG6_MARK'
+]
+
+nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']
+nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
+valid_groups = [
+ 'address_group',
+ 'network_group',
+ 'port_group'
+]
+
+snmp_change_type = {
+ 'unknown': 0,
+ 'add': 1,
+ 'delete': 2,
+ 'change': 3
+}
+snmp_event_source = 1
+snmp_trap_mib = 'VYATTA-TRAP-MIB'
+snmp_trap_name = 'mgmtEventTrap'
+
+def get_firewall_interfaces(conf):
+ out = {}
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ def find_interfaces(iftype_conf, output={}, prefix=''):
+ for ifname, if_conf in iftype_conf.items():
+ if 'firewall' in if_conf:
+ output[prefix + ifname] = if_conf['firewall']
+ for vif in ['vif', 'vif_s', 'vif_c']:
+ if vif in if_conf:
+ output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
+ return output
+ for iftype, iftype_conf in interfaces.items():
+ out.update(find_interfaces(iftype_conf))
+ return out
+
+def get_firewall_zones(conf):
+ used_v4 = []
+ used_v6 = []
+ zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if 'zone' in zone_policy:
+ for zone, zone_conf in zone_policy['zone'].items():
+ if 'from' in zone_conf:
+ for from_zone, from_conf in zone_conf['from'].items():
+ name = dict_search_args(from_conf, 'firewall', 'name')
+ if name:
+ used_v4.append(name)
+
+ ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+ if ipv6_name:
+ used_v6.append(ipv6_name)
+
+ if 'intra_zone_filtering' in zone_conf:
+ name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name')
+ if name:
+ used_v4.append(name)
+
+ ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name')
+ if ipv6_name:
+ used_v6.append(ipv6_name)
+
+ return {'name': used_v4, 'ipv6_name': used_v6}
+
+def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['nfirewall']
+ base = ['firewall']
+
firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- pprint(firewall)
+ default_values = defaults(base)
+ firewall = dict_merge(default_values, firewall)
+
+ firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
+ firewall['interfaces'] = get_firewall_interfaces(conf)
+ firewall['zone_policy'] = get_firewall_zones(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)
+
return firewall
+def verify_rule(firewall, rule_conf, ipv6):
+ if 'action' not in rule_conf:
+ raise ConfigError('Rule action must be defined')
+
+ if 'fragment' in rule_conf:
+ if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
+ raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
+
+ if 'ipsec' in rule_conf:
+ if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']):
+ raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"')
+
+ if 'recent' in rule_conf:
+ if not {'count', 'time'} <= set(rule_conf['recent']):
+ raise ConfigError('Recent "count" and "time" values must be defined')
+
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
+ if 'protocol' in rule_conf:
+ if rule_conf['protocol'] == 'icmp' and ipv6:
+ raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp')
+ if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6:
+ raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ side_conf = rule_conf[side]
+
+ if 'group' in side_conf:
+ if {'address_group', 'network_group'} <= set(side_conf['group']):
+ raise ConfigError('Only one address-group or network-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+
+ if group_name and group_name[0] == '!':
+ group_name = group_name[1:]
+
+ fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ error_group = fw_group.replace("_", "-")
+ group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+
+ if not group_obj:
+ print(f'WARNING: {error_group} "{group_name}" has no members')
+
+ if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
+ if 'protocol' not in rule_conf:
+ raise ConfigError('Protocol must be defined if specifying a port or port-group')
+
+ if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
+
def verify(firewall):
- # bail out early - looks like removal from running config
- if not firewall:
- return None
+ 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')
+
+ for name in ['name', 'ipv6_name']:
+ if name in firewall:
+ for name_id, name_conf in firewall[name].items():
+ if name_id in preserve_chains:
+ raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS')
+
+ if name_id.startswith("VZONE"):
+ raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix')
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ verify_rule(firewall, rule_conf, name == 'ipv6_name')
+
+ for ifname, if_firewall in firewall['interfaces'].items():
+ 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'Firewall name "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
+ raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+
+ for fw_name, used_names in firewall['zone_policy'].items():
+ for name in used_names:
+ if dict_search_args(firewall, fw_name, name) == None:
+ raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
return None
+def cleanup_rule(table, jump_chain):
+ commands = []
+ chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
+ for chain in chains:
+ results = cmd(f'nft -a list chain {table} {chain}').split("\n")
+ for line in results:
+ if f'jump {jump_chain}' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
+ return commands
+
+def cleanup_commands(firewall):
+ commands = []
+ commands_end = []
+ for table in ['ip filter', 'ip6 filter']:
+ state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
+ json_str = cmd(f'nft -j list table {table}')
+ obj = loads(json_str)
+ if 'nftables' not in obj:
+ continue
+ for item in obj['nftables']:
+ if 'chain' in item:
+ chain = item['chain']['name']
+ if chain in ['VYOS_STATE_POLICY', 'VYOS_STATE_POLICY6']:
+ if 'state_policy' not in firewall:
+ commands.append(f'delete chain {table} {chain}')
+ else:
+ commands.append(f'flush chain {table} {chain}')
+ elif chain not in preserve_chains and not chain.startswith("VZONE"):
+ if table == 'ip filter' and dict_search_args(firewall, 'name', chain.replace(NAME_PREFIX, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain.replace(NAME6_PREFIX, "", 1)) != None:
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands += cleanup_rule(table, chain)
+ commands.append(f'delete chain {table} {chain}')
+ elif 'rule' in item:
+ rule = item['rule']
+ if rule['chain'] in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL', 'VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
+ if 'expr' in rule and any([True for expr in rule['expr'] if dict_search_args(expr, 'jump', 'target') == state_chain]):
+ if 'state_policy' not in firewall:
+ chain = rule['chain']
+ handle = rule['handle']
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+ elif 'set' in item:
+ set_name = item['set']['name']
+ commands_end.append(f'delete set {table} {set_name}')
+ return commands + commands_end
+
def generate(firewall):
- if not firewall:
- return None
+ if not os.path.exists(nftables_conf):
+ firewall['first_install'] = True
+ else:
+ firewall['cleanup_commands'] = cleanup_commands(firewall)
+ render(nftables_conf, 'firewall/nftables.tmpl', firewall)
+ render(nftables_defines_conf, 'firewall/nftables-defines.tmpl', firewall)
return None
-def apply(firewall):
- if not firewall:
+def apply_sysfs(firewall):
+ for name, conf in sysfs_config.items():
+ paths = glob(conf['sysfs'])
+ value = None
+
+ if name in firewall:
+ conf_value = firewall[name]
+
+ if conf_value in conf:
+ value = conf[conf_value]
+ elif conf_value == 'enable':
+ value = '1'
+ elif conf_value == 'disable':
+ value = '0'
+
+ if value:
+ for path in paths:
+ 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 state_policy_rule_exists():
+ # Determine if state policy rules already exist in nft
+ search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD')
+ return 'VYOS_STATE_POLICY' in search_str
+
+def resync_policy_route():
+ # Update policy route as firewall groups were updated
+ tmp = run(policy_route_conf_script)
+ if tmp > 0:
+ print('Warning: Failed to re-apply policy route configuration')
+
+def apply(firewall):
+ if 'first_install' in firewall:
+ run('nfct helper add rpc inet tcp')
+ run('nfct helper add rpc inet udp')
+ run('nfct helper add tns inet tcp')
+
+ install_result = run(f'nft -f {nftables_conf}')
+ if install_result == 1:
+ raise ConfigError('Failed to apply firewall')
+
+ if 'state_policy' in firewall and not state_policy_rule_exists():
+ for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
+ cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
+
+ for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
+ cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
+
+ apply_sysfs(firewall)
+
+ if firewall['policy_resync']:
+ resync_policy_route()
+
+ 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 e01f3066b..975f19acf 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -35,67 +35,64 @@ from vyos import airbag
airbag.enable()
uacctd_conf_path = '/run/pmacct/uacctd.conf'
-iptables_nflog_table = 'raw'
-iptables_nflog_chain = 'VYATTA_CT_PREROUTING_HOOK'
-egress_iptables_nflog_table = 'mangle'
-egress_iptables_nflog_chain = 'FORWARD'
+nftables_nflog_table = 'raw'
+nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK'
+egress_nftables_nflog_table = 'inet mangle'
+egress_nftables_nflog_chain = 'FORWARD'
-# get iptables rule dict for chain in table
-def _iptables_get_nflog(chain, table):
+# get nftables rule dict for chain in table
+def _nftables_get_nflog(chain, table):
# define list with rules
rules = []
# prepare regex for parsing rules
- rule_pattern = "^-A (?P<rule_definition>{0} (\-i|\-o) (?P<interface>[\w\.\*\-]+).*--comment FLOW_ACCOUNTING_RULE.* -j NFLOG.*$)".format(chain)
+ rule_pattern = '[io]ifname "(?P<interface>[\w\.\*\-]+)".*handle (?P<handle>[\d]+)'
rule_re = re.compile(rule_pattern)
- for iptables_variant in ['iptables', 'ip6tables']:
- # run iptables, save output and split it by lines
- iptables_command = f'{iptables_variant} -t {table} -S {chain}'
- tmp = cmd(iptables_command, message='Failed to get flows list')
-
- # parse each line and add information to list
- for current_rule in tmp.splitlines():
- current_rule_parsed = rule_re.search(current_rule)
- if current_rule_parsed:
- rules.append({ 'interface': current_rule_parsed.groupdict()["interface"], 'iptables_variant': iptables_variant, 'table': table, 'rule_definition': current_rule_parsed.groupdict()["rule_definition"] })
+ # run nftables, save output and split it by lines
+ nftables_command = f'nft -a list chain {table} {chain}'
+ tmp = cmd(nftables_command, message='Failed to get flows list')
+ # parse each line and add information to list
+ for current_rule in tmp.splitlines():
+ if 'FLOW_ACCOUNTING_RULE' not in current_rule:
+ continue
+ current_rule_parsed = rule_re.search(current_rule)
+ if current_rule_parsed:
+ groups = current_rule_parsed.groupdict()
+ rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] })
# return list with rules
return rules
-# modify iptables rules
-def _iptables_config(configured_ifaces, direction, length=None):
- # define list of iptables commands to modify settings
- iptable_commands = []
- iptables_chain = iptables_nflog_chain
- iptables_table = iptables_nflog_table
+def _nftables_config(configured_ifaces, direction, length=None):
+ # define list of nftables commands to modify settings
+ nftable_commands = []
+ nftables_chain = nftables_nflog_chain
+ nftables_table = nftables_nflog_table
if direction == "egress":
- iptables_chain = egress_iptables_nflog_chain
- iptables_table = egress_iptables_nflog_table
+ nftables_chain = egress_nftables_nflog_chain
+ nftables_table = egress_nftables_nflog_table
# prepare extended list with configured interfaces
configured_ifaces_extended = []
for iface in configured_ifaces:
- configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'iptables' })
- configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'ip6tables' })
+ configured_ifaces_extended.append({ 'iface': iface })
- # get currently configured interfaces with iptables rules
- active_nflog_rules = _iptables_get_nflog(iptables_chain, iptables_table)
+ # get currently configured interfaces with nftables rules
+ active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table)
# compare current active list with configured one and delete excessive interfaces, add missed
active_nflog_ifaces = []
for rule in active_nflog_rules:
- iptables = rule['iptables_variant']
interface = rule['interface']
if interface not in configured_ifaces:
table = rule['table']
- rule = rule['rule_definition']
- iptable_commands.append(f'{iptables} -t {table} -D {rule}')
+ handle = rule['handle']
+ nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}')
else:
active_nflog_ifaces.append({
'iface': interface,
- 'iptables_variant': iptables,
})
# do not create new rules for already configured interfaces
@@ -106,16 +103,12 @@ def _iptables_config(configured_ifaces, direction, length=None):
# create missed rules
for iface_extended in configured_ifaces_extended:
iface = iface_extended['iface']
- iptables = iface_extended['iptables_variant']
- iptables_op = "-i"
- if direction == "egress":
- iptables_op = "-o"
-
- rule_definition = f'{iptables_chain} {iptables_op} {iface} -m comment --comment FLOW_ACCOUNTING_RULE -j NFLOG --nflog-group 2 --nflog-size {length} --nflog-threshold 100'
- iptable_commands.append(f'{iptables} -t {iptables_table} -I {rule_definition}')
+ iface_prefix = "o" if direction == "egress" else "i"
+ rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"'
+ nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}')
- # change iptables
- for command in iptable_commands:
+ # change nftables
+ for command in nftable_commands:
cmd(command, raising=ConfigError)
@@ -249,8 +242,8 @@ def apply(flow_config):
action = 'restart'
# Check if flow-accounting was removed and define command
if not flow_config:
- _iptables_config([], 'ingress')
- _iptables_config([], 'egress')
+ _nftables_config([], 'ingress')
+ _nftables_config([], 'egress')
# Stop flow-accounting daemon and remove configuration file
cmd('systemctl stop uacctd.service')
@@ -261,15 +254,15 @@ def apply(flow_config):
# Start/reload flow-accounting daemon
cmd(f'systemctl restart uacctd.service')
- # configure iptables rules for defined interfaces
+ # configure nftables rules for defined interfaces
if 'interface' in flow_config:
- _iptables_config(flow_config['interface'], 'ingress', flow_config['packet_length'])
+ _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length'])
# configure egress the same way if configured otherwise remove it
if 'enable_egress' in flow_config:
- _iptables_config(flow_config['interface'], 'egress', flow_config['packet_length'])
+ _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length'])
else:
- _iptables_config([], 'egress')
+ _nftables_config([], 'egress')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/high-availability.py
index c72efc61f..7d51bb393 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/high-availability.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2021 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -40,33 +40,41 @@ def get_config(config=None):
else:
conf = Config()
- base = ['high-availability', 'vrrp']
+ base = ['high-availability']
+ base_vrrp = ['high-availability', 'vrrp']
if not conf.exists(base):
return None
- vrrp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ ha = 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.
- if 'group' in vrrp:
- default_values = defaults(base + ['group'])
- for group in vrrp['group']:
- vrrp['group'][group] = dict_merge(default_values, vrrp['group'][group])
+ if 'vrrp' in ha:
+ if 'group' in ha['vrrp']:
+ default_values_vrrp = defaults(base_vrrp + ['group'])
+ for group in ha['vrrp']['group']:
+ ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group])
+
+ # Merge per virtual-server default values
+ if 'virtual_server' in ha:
+ default_values = defaults(base + ['virtual-server'])
+ for vs in ha['virtual_server']:
+ ha['virtual_server'][vs] = dict_merge(default_values, ha['virtual_server'][vs])
## Get the sync group used for conntrack-sync
conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group']
if conf.exists(conntrack_path):
- vrrp['conntrack_sync_group'] = conf.return_value(conntrack_path)
+ ha['conntrack_sync_group'] = conf.return_value(conntrack_path)
- return vrrp
+ return ha
-def verify(vrrp):
- if not vrrp:
+def verify(ha):
+ if not ha:
return None
used_vrid_if = []
- if 'group' in vrrp:
- for group, group_config in vrrp['group'].items():
+ if 'vrrp' in ha and 'group' in ha['vrrp']:
+ for group, group_config in ha['vrrp']['group'].items():
# Check required fields
if 'vrid' not in group_config:
raise ConfigError(f'VRID is required but not set in VRRP group "{group}"')
@@ -119,24 +127,37 @@ def verify(vrrp):
if is_ipv4(group_config['peer_address']):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
# Check sync groups
- if 'sync_group' in vrrp:
- for sync_group, sync_config in vrrp['sync_group'].items():
+ if 'vrrp' in ha and 'sync_group' in ha['vrrp']:
+ for sync_group, sync_config in ha['vrrp']['sync_group'].items():
if 'member' in sync_config:
for member in sync_config['member']:
- if member not in vrrp['group']:
+ if member not in ha['vrrp']['group']:
raise ConfigError(f'VRRP sync-group "{sync_group}" refers to VRRP group "{member}", '\
'but it does not exist!')
-def generate(vrrp):
- if not vrrp:
+ # Virtual-server
+ if 'virtual_server' in ha:
+ for vs, vs_config in ha['virtual_server'].items():
+ if 'port' not in vs_config:
+ raise ConfigError(f'Port is required but not set for virtual-server "{vs}"')
+ if 'real_server' not in vs_config:
+ raise ConfigError(f'Real-server ip is required but not set for virtual-server "{vs}"')
+ # Real-server
+ for rs, rs_config in vs_config['real_server'].items():
+ if 'port' not in rs_config:
+ raise ConfigError(f'Port is required but not set for virtual-server "{vs}" real-server "{rs}"')
+
+
+def generate(ha):
+ if not ha:
return None
- render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl', vrrp)
+ render(VRRP.location['config'], 'high-availability/keepalived.conf.tmpl', ha)
return None
-def apply(vrrp):
+def apply(ha):
service_name = 'keepalived.service'
- if not vrrp:
+ if not ha:
call(f'systemctl stop {service_name}')
return None
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 59939d0fb..9f319fc8a 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -28,6 +28,7 @@ from vyos.configdict import dict_merge
from vyos.template import render
from vyos.template import is_ip_network
from vyos.util import cmd
+from vyos.util import run
from vyos.util import check_kmod
from vyos.util import dict_search
from vyos.validate import is_addr_assigned
@@ -42,7 +43,7 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'):
else:
k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
-iptables_nat_config = '/tmp/vyos-nat-rules.nft'
+nftables_nat_config = '/tmp/vyos-nat-rules.nft'
def get_handler(json, chain, target):
""" Get nftable rule handler number of given chain/target combination.
@@ -93,7 +94,6 @@ def get_config(config=None):
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 raw')
nftable_json = json.loads(tmp)
@@ -106,9 +106,9 @@ def get_config(config=None):
nat['helper_functions'] = 'remove'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
return nat
@@ -119,10 +119,10 @@ def get_config(config=None):
nat['helper_functions'] = 'add'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
return nat
@@ -180,14 +180,21 @@ def verify(nat):
return None
def generate(nat):
- render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat,
- permission=0o755)
+ render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft -c -f {nftables_nat_config}')
+ if tmp > 0:
+ if os.path.exists(nftables_ct_file):
+ os.unlink(nftables_ct_file)
+ raise ConfigError('Configuration file errors encountered!')
+
return None
def apply(nat):
- cmd(f'{iptables_nat_config}')
- if os.path.isfile(iptables_nat_config):
- os.unlink(iptables_nat_config)
+ cmd(f'nft -f {nftables_nat_config}')
+ if os.path.isfile(nftables_nat_config):
+ os.unlink(nftables_nat_config)
return None
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index fb376a434..8bf2e8073 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -35,7 +35,7 @@ airbag.enable()
k_mod = ['nft_nat', 'nft_chain_nat']
-iptables_nat_config = '/tmp/vyos-nat66-rules.nft'
+nftables_nat66_config = '/tmp/vyos-nat66-rules.nft'
ndppd_config = '/run/ndppd/ndppd.conf'
def get_handler(json, chain, target):
@@ -79,9 +79,9 @@ def get_config(config=None):
if not conf.exists(base):
nat['helper_functions'] = 'remove'
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
return nat
@@ -92,10 +92,10 @@ def get_config(config=None):
nat['helper_functions'] = 'add'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
else:
nat['helper_functions'] = 'has'
@@ -145,22 +145,22 @@ def verify(nat):
return None
def generate(nat):
- render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
+ render(nftables_nat66_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
render(ndppd_config, 'ndppd/ndppd.conf.tmpl', nat, permission=0o755)
return None
def apply(nat):
if not nat:
return None
- cmd(f'{iptables_nat_config}')
+ cmd(f'{nftables_nat66_config}')
if 'deleted' in nat or not dict_search('source.rule', nat):
cmd('systemctl stop ndppd')
if os.path.isfile(ndppd_config):
os.unlink(ndppd_config)
else:
cmd('systemctl restart ndppd')
- if os.path.isfile(iptables_nat_config):
- os.unlink(iptables_nat_config)
+ if os.path.isfile(nftables_nat66_config):
+ os.unlink(nftables_nat66_config)
return None
diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py
index 539189442..3f834f55c 100755
--- a/src/conf_mode/policy-local-route.py
+++ b/src/conf_mode/policy-local-route.py
@@ -18,6 +18,7 @@ import os
from sys import exit
+from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
@@ -35,35 +36,92 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['policy', 'local-route']
+ base = ['policy']
+
pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- # delete policy local-route
- dict = {}
- tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_'))
- if tmp:
- for rule in (tmp or []):
- src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
- fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark'])
- if src:
- dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
- pbr.update(dict)
- if fwmk:
- dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict)
+ for route in ['local_route', 'local_route6']:
+ dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove'
+ route_key = 'local-route' if route == 'local_route' else 'local-route6'
+ base_rule = base + [route_key, 'rule']
+
+ # delete policy local-route
+ dict = {}
+ tmp = node_changed(conf, base_rule, key_mangling=('-', '_'))
+ if tmp:
+ for rule in (tmp or []):
+ src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+ fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+ iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+ dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ rule_def = {}
+ if src:
+ rule_def = dict_merge({'source' : src}, rule_def)
+ if fwmk:
+ rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+ if iif:
+ rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+ if dst:
+ rule_def = dict_merge({'destination' : dst}, rule_def)
+ dict = dict_merge({dict_id : {rule : rule_def}}, dict)
pbr.update(dict)
- # delete policy local-route rule x source x.x.x.x
- # delete policy local-route rule x fwmark x
- if 'rule' in pbr:
- for rule in pbr['rule']:
- src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source'])
- fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark'])
- if src:
- dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict)
- pbr.update(dict)
- if fwmk:
- dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict)
- pbr.update(dict)
+ if not route in pbr:
+ continue
+
+ # delete policy local-route rule x source x.x.x.x
+ # delete policy local-route rule x fwmark x
+ # delete policy local-route rule x destination x.x.x.x
+ if 'rule' in pbr[route]:
+ for rule, rule_config in pbr[route]['rule'].items():
+ src = leaf_node_changed(conf, base_rule + [rule, 'source'])
+ fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])
+ iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])
+ dst = leaf_node_changed(conf, base_rule + [rule, 'destination'])
+ # keep track of changes in configuration
+ # otherwise we might remove an existing node although nothing else has changed
+ changed = False
+
+ rule_def = {}
+ # src is None if there are no changes to src
+ if src is None:
+ # if src hasn't changed, include it in the removal selector
+ # if a new selector is added, we have to remove all previous rules without this selector
+ # to make sure we remove all previous rules with this source(s), it will be included
+ if 'source' in rule_config:
+ rule_def = dict_merge({'source': rule_config['source']}, rule_def)
+ else:
+ # if src is not None, it's previous content will be returned
+ # this can be an empty array if it's just being set, or the previous value
+ # either way, something has to be changed and we only want to remove previous values
+ changed = True
+ # set the old value for removal if it's not empty
+ if len(src) > 0:
+ rule_def = dict_merge({'source' : src}, rule_def)
+ if fwmk is None:
+ if 'fwmark' in rule_config:
+ rule_def = dict_merge({'fwmark': rule_config['fwmark']}, rule_def)
+ else:
+ changed = True
+ if len(fwmk) > 0:
+ rule_def = dict_merge({'fwmark' : fwmk}, rule_def)
+ if iif is None:
+ if 'inbound_interface' in rule_config:
+ rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def)
+ else:
+ changed = True
+ if len(iif) > 0:
+ rule_def = dict_merge({'inbound_interface' : iif}, rule_def)
+ if dst is None:
+ if 'destination' in rule_config:
+ rule_def = dict_merge({'destination': rule_config['destination']}, rule_def)
+ else:
+ changed = True
+ if len(dst) > 0:
+ rule_def = dict_merge({'destination' : dst}, rule_def)
+ if changed:
+ dict = dict_merge({dict_id : {rule : rule_def}}, dict)
+ pbr.update(dict)
return pbr
@@ -72,13 +130,25 @@ def verify(pbr):
if not pbr:
return None
- if 'rule' in pbr:
- for rule in pbr['rule']:
- if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]:
- raise ConfigError('Source address or fwmark is required!')
- else:
- if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']:
- raise ConfigError('Table set is required!')
+ for route in ['local_route', 'local_route6']:
+ if not route in pbr:
+ continue
+
+ pbr_route = pbr[route]
+ if 'rule' in pbr_route:
+ for rule in pbr_route['rule']:
+ if 'source' not in pbr_route['rule'][rule] \
+ and 'destination' not in pbr_route['rule'][rule] \
+ and 'fwmark' not in pbr_route['rule'][rule] \
+ and 'inbound_interface' not in pbr_route['rule'][rule]:
+ raise ConfigError('Source or destination address or fwmark or inbound-interface is required!')
+ else:
+ if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']:
+ raise ConfigError('Table set is required!')
+ if 'inbound_interface' in pbr_route['rule'][rule]:
+ interface = pbr_route['rule'][rule]['inbound_interface']
+ if interface not in interfaces():
+ raise ConfigError(f'Interface "{interface}" does not exist')
return None
@@ -93,36 +163,51 @@ def apply(pbr):
return None
# Delete old rule if needed
- if 'rule_remove' in pbr:
- for rule in pbr['rule_remove']:
- if 'source' in pbr['rule_remove'][rule]:
- for src in pbr['rule_remove'][rule]['source']:
- call(f'ip rule del prio {rule} from {src}')
- if 'fwmark' in pbr['rule_remove'][rule]:
- for fwmk in pbr['rule_remove'][rule]['fwmark']:
- call(f'ip rule del prio {rule} from all fwmark {fwmk}')
+ for rule_rm in ['rule_remove', 'rule6_remove']:
+ if rule_rm in pbr:
+ v6 = " -6" if rule_rm == 'rule6_remove' else ""
+ for rule, rule_config in pbr[rule_rm].items():
+ rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['']
+ for src in rule_config['source']:
+ f_src = '' if src == '' else f' from {src} '
+ rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['']
+ for dst in rule_config['destination']:
+ f_dst = '' if dst == '' else f' to {dst} '
+ rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else ['']
+ for fwmk in rule_config['fwmark']:
+ f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} '
+ rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else ['']
+ for iif in rule_config['inbound_interface']:
+ f_iif = '' if iif == '' else f' iif {iif} '
+ call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')
# Generate new config
- if 'rule' in pbr:
- for rule in pbr['rule']:
- table = pbr['rule'][rule]['set']['table']
- # Only source in the rule
- # set policy local-route rule 100 source '203.0.113.1'
- if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]:
- for src in pbr['rule'][rule]['source']:
- call(f'ip rule add prio {rule} from {src} lookup {table}')
- # Only fwmark in the rule
- # set policy local-route rule 101 fwmark '23'
- if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]:
- fwmk = pbr['rule'][rule]['fwmark']
- call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}')
- # Source and fwmark in the rule
- # set policy local-route rule 100 source '203.0.113.1'
- # set policy local-route rule 100 fwmark '23'
- if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]:
- fwmk = pbr['rule'][rule]['fwmark']
- for src in pbr['rule'][rule]['source']:
- call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}')
+ for route in ['local_route', 'local_route6']:
+ if not route in pbr:
+ continue
+
+ v6 = " -6" if route == 'local_route6' else ""
+
+ pbr_route = pbr[route]
+ if 'rule' in pbr_route:
+ for rule, rule_config in pbr_route['rule'].items():
+ table = rule_config['set']['table']
+
+ rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all']
+ for src in rule_config['source'] or ['all']:
+ f_src = '' if src == '' else f' from {src} '
+ rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all']
+ for dst in rule_config['destination']:
+ f_dst = '' if dst == '' else f' to {dst} '
+ f_fwmk = ''
+ if 'fwmark' in rule_config:
+ fwmk = rule_config['fwmark']
+ f_fwmk = f' fwmark {fwmk} '
+ f_iif = ''
+ if 'inbound_interface' in rule_config:
+ iif = rule_config['inbound_interface']
+ f_iif = f' iif {iif} '
+ call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}')
return None
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
new file mode 100755
index 000000000..1108aebe6
--- /dev/null
+++ b/src/conf_mode/policy-route-interface.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.config import Config
+from vyos.ifconfig import Section
+from vyos.template import render
+from vyos.util import cmd
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ ifname = argv[1]
+ ifpath = Section.get_config_path(ifname)
+ if_policy_path = f'interfaces {ifpath} policy'
+
+ if_policy = conf.get_config_dict(if_policy_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if_policy['ifname'] = ifname
+ if_policy['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return if_policy
+
+def verify(if_policy):
+ # bail out early - looks like removal from running config
+ if not if_policy:
+ return None
+
+ for route in ['route', 'route6']:
+ if route in if_policy:
+ if route not in if_policy['policy']:
+ raise ConfigError('Policy route not configured')
+
+ route_name = if_policy[route]
+
+ if route_name not in if_policy['policy'][route]:
+ raise ConfigError(f'Invalid policy route name "{name}"')
+
+ return None
+
+def generate(if_policy):
+ return None
+
+def cleanup_rule(table, chain, ifname, new_name=None):
+ results = cmd(f'nft -a list chain {table} {chain}').split("\n")
+ retval = None
+ for line in results:
+ if f'ifname "{ifname}"' in line:
+ if new_name and f'jump {new_name}' in line:
+ # new_name is used to clear rules for any previously referenced chains
+ # returns true when rule exists and doesn't need to be created
+ retval = True
+ continue
+
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ cmd(f'nft delete rule {table} {chain} handle {handle_search[1]}')
+ return retval
+
+def apply(if_policy):
+ ifname = if_policy['ifname']
+
+ route_chain = 'VYOS_PBR_PREROUTING'
+ ipv6_route_chain = 'VYOS_PBR6_PREROUTING'
+
+ if 'route' in if_policy:
+ name = 'VYOS_PBR_' + if_policy['route']
+ rule_exists = cleanup_rule('ip mangle', route_chain, ifname, name)
+
+ if not rule_exists:
+ cmd(f'nft insert rule ip mangle {route_chain} iifname {ifname} counter jump {name}')
+ else:
+ cleanup_rule('ip mangle', route_chain, ifname)
+
+ if 'route6' in if_policy:
+ name = 'VYOS_PBR6_' + if_policy['route6']
+ rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name)
+
+ if not rule_exists:
+ cmd(f'nft insert rule ip6 mangle {ipv6_route_chain} iifname {ifname} counter jump {name}')
+ else:
+ cleanup_rule('ip6 mangle', ipv6_route_chain, ifname)
+
+ return None
+
+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/policy-route.py b/src/conf_mode/policy-route.py
new file mode 100755
index 000000000..82f668acf
--- /dev/null
+++ b/src/conf_mode/policy-route.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import re
+
+from json import loads
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.util import cmd
+from vyos.util import dict_search_args
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+mark_offset = 0x7FFFFFFF
+nftables_conf = '/run/nftables_policy.conf'
+
+preserve_chains = [
+ 'VYOS_PBR_PREROUTING',
+ 'VYOS_PBR_POSTROUTING',
+ 'VYOS_PBR6_PREROUTING',
+ 'VYOS_PBR6_POSTROUTING'
+]
+
+valid_groups = [
+ 'address_group',
+ 'network_group',
+ 'port_group'
+]
+
+def get_policy_interfaces(conf):
+ out = {}
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ def find_interfaces(iftype_conf, output={}, prefix=''):
+ for ifname, if_conf in iftype_conf.items():
+ if 'policy' in if_conf:
+ output[prefix + ifname] = if_conf['policy']
+ for vif in ['vif', 'vif_s', 'vif_c']:
+ if vif in if_conf:
+ output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
+ return output
+ for iftype, iftype_conf in interfaces.items():
+ out.update(find_interfaces(iftype_conf))
+ return out
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['policy']
+
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ policy['interfaces'] = get_policy_interfaces(conf)
+
+ return policy
+
+def verify_rule(policy, name, rule_conf, ipv6):
+ icmp = 'icmp' if not ipv6 else 'icmpv6'
+ if icmp in rule_conf:
+ icmp_defined = False
+ if 'type_name' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name')
+ if 'code' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'type' not in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined')
+ if 'type' in rule_conf[icmp]:
+ icmp_defined = True
+
+ if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP')
+
+ if 'set' in rule_conf:
+ if 'tcp_mss' in rule_conf['set']:
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if not tcp_flags or 'syn' not in tcp_flags:
+ raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
+
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ side_conf = rule_conf[side]
+
+ if 'group' in side_conf:
+ if {'address_group', 'network_group'} <= set(side_conf['group']):
+ raise ConfigError('Only one address-group or network-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+ fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ error_group = fw_group.replace("_", "-")
+ group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule')
+
+ if not group_obj:
+ print(f'WARNING: {error_group} "{group_name}" has no members')
+
+ if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
+ if 'protocol' not in rule_conf:
+ raise ConfigError('Protocol must be defined if specifying a port or port-group')
+
+ if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
+
+def verify(policy):
+ for route in ['route', 'route6']:
+ ipv6 = route == 'route6'
+ if route in policy:
+ for name, pol_conf in policy[route].items():
+ if 'rule' in pol_conf:
+ for rule_id, rule_conf in pol_conf['rule'].items():
+ verify_rule(policy, name, rule_conf, ipv6)
+
+ for ifname, if_policy in policy['interfaces'].items():
+ name = dict_search_args(if_policy, 'route')
+ ipv6_name = dict_search_args(if_policy, 'route6')
+
+ if name and not dict_search_args(policy, 'route', name):
+ raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name):
+ raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}')
+
+ return None
+
+def cleanup_rule(table, jump_chain):
+ commands = []
+ results = cmd(f'nft -a list table {table}').split("\n")
+ for line in results:
+ if f'jump {jump_chain}' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
+ return commands
+
+def cleanup_commands(policy):
+ commands = []
+ for table in ['ip mangle', 'ip6 mangle']:
+ json_str = cmd(f'nft -j list table {table}')
+ obj = loads(json_str)
+ if 'nftables' not in obj:
+ continue
+ for item in obj['nftables']:
+ if 'chain' in item:
+ chain = item['chain']['name']
+ if not chain.startswith("VYOS_PBR"):
+ continue
+ if chain not in preserve_chains:
+ if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands += cleanup_rule(table, chain)
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+
+def generate(policy):
+ if not os.path.exists(nftables_conf):
+ policy['first_install'] = True
+ else:
+ policy['cleanup_commands'] = cleanup_commands(policy)
+
+ render(nftables_conf, 'firewall/nftables-policy.tmpl', policy)
+ return None
+
+def apply_table_marks(policy):
+ for route in ['route', 'route6']:
+ if route in policy:
+ cmd_str = 'ip' if route == 'route' else 'ip -6'
+ tables = []
+ for name, pol_conf in policy[route].items():
+ if 'rule' in pol_conf:
+ for rule_id, rule_conf in pol_conf['rule'].items():
+ set_table = dict_search_args(rule_conf, 'set', 'table')
+ if set_table:
+ if set_table == 'main':
+ set_table = '254'
+ if set_table in tables:
+ continue
+ tables.append(set_table)
+ table_mark = mark_offset - int(set_table)
+ cmd(f'{cmd_str} rule add pref {set_table} fwmark {table_mark} table {set_table}')
+
+def cleanup_table_marks():
+ for cmd_str in ['ip', 'ip -6']:
+ json_rules = cmd(f'{cmd_str} -j -N rule list')
+ rules = loads(json_rules)
+ for rule in rules:
+ if 'fwmark' not in rule or 'table' not in rule:
+ continue
+ fwmark = rule['fwmark']
+ table = int(rule['table'])
+ if fwmark[:2] == '0x':
+ fwmark = int(fwmark, 16)
+ if (int(fwmark) == (mark_offset - table)):
+ cmd(f'{cmd_str} rule del fwmark {fwmark} table {table}')
+
+def apply(policy):
+ install_result = run(f'nft -f {nftables_conf}')
+ if install_result == 1:
+ raise ConfigError('Failed to apply policy based routing')
+
+ if 'first_install' not in policy:
+ cleanup_table_marks()
+
+ apply_table_marks(policy)
+
+ return None
+
+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/policy.py b/src/conf_mode/policy.py
index e251396c7..6b1d3bf1a 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -87,6 +87,7 @@ def verify(policy):
# human readable instance name (hypen instead of underscore)
policy_hr = policy_type.replace('_', '-')
+ entries = []
for rule, rule_config in instance_config['rule'].items():
mandatory_error = f'must be specified for "{policy_hr} {instance} rule {rule}"!'
if 'action' not in rule_config:
@@ -113,6 +114,11 @@ def verify(policy):
if 'prefix' not in rule_config:
raise ConfigError(f'A prefix {mandatory_error}')
+ # Check prefix duplicates
+ if rule_config['prefix'] in entries and ('ge' not in rule_config and 'le' not in rule_config):
+ raise ConfigError(f'Prefix {rule_config["prefix"]} is duplicated!')
+ entries.append(rule_config['prefix'])
+
# route-maps tend to be a bit more complex so they get their own verify() section
if 'route_map' in policy:
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 12dacdba0..7eeb5cd30 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -16,6 +16,8 @@
from vyos.config import Config
from vyos.configdict import node_changed
+from vyos.firewall import find_nftables_rule
+from vyos.firewall import remove_nftables_rule
from vyos.template import render
from vyos.util import process_named_running
from vyos.util import run
@@ -88,24 +90,19 @@ def generate(nhrp):
def apply(nhrp):
if 'tunnel' in nhrp:
for tunnel, tunnel_conf in nhrp['tunnel'].items():
- if 'source_address' in tunnel_conf:
- chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
- source_address = tunnel_conf['source_address']
+ if 'source_address' in nhrp['if_tunnel'][tunnel]:
+ comment = f'VYOS_NHRP_{tunnel}'
+ source_address = nhrp['if_tunnel'][tunnel]['source_address']
- chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
- if not chain_exists:
- run(f'sudo iptables --new {chain}')
- run(f'sudo iptables --append {chain} -p gre -s {source_address} -d 224.0.0.0/4 -j DROP')
- run(f'sudo iptables --append {chain} -j RETURN')
- run(f'sudo iptables --insert OUTPUT 2 -j {chain}')
+ rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4'])
+ if not rule_handle:
+ run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"')
for tunnel in nhrp['del_tunnels']:
- chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
- chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
- if chain_exists:
- run(f'sudo iptables --delete OUTPUT -j {chain}')
- run(f'sudo iptables --flush {chain}')
- run(f'sudo iptables --delete-chain {chain}')
+ comment = f'VYOS_NHRP_{tunnel}'
+ rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"'])
+ if rule_handle:
+ remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
run(f'systemctl {action} opennhrp')
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
new file mode 100755
index 000000000..8a972b9fe
--- /dev/null
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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 json
+
+from sys import exit
+from shutil import rmtree
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.ifconfig import Section
+from vyos.template import render
+from vyos.util import call
+from vyos.util import chown
+from vyos.util import cmd
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+
+base_dir = '/run/telegraf'
+cache_dir = f'/etc/telegraf/.cache'
+config_telegraf = f'{base_dir}/vyos-telegraf.conf'
+custom_scripts_dir = '/etc/telegraf/custom_scripts'
+syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf'
+systemd_telegraf_service = '/etc/systemd/system/vyos-telegraf.service'
+systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d'
+systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf'
+
+
+def get_interfaces(type='', vlan=True):
+ """
+ Get interfaces
+ get_interfaces()
+ ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
+
+ get_interfaces("dummy")
+ ['dum0']
+ """
+ interfaces = []
+ ifaces = Section.interfaces(type)
+ for iface in ifaces:
+ if vlan == False and '.' in iface:
+ continue
+ interfaces.append(iface)
+
+ return interfaces
+
+def get_nft_filter_chains():
+ """
+ Get nft chains for table filter
+ """
+ nft = cmd('nft --json list table ip filter')
+ nft = json.loads(nft)
+ chain_list = []
+
+ for output in nft['nftables']:
+ if 'chain' in output:
+ chain = output['chain']['name']
+ chain_list.append(chain)
+
+ return chain_list
+
+
+def get_config(config=None):
+
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'monitoring', 'telegraf']
+ if not conf.exists(base):
+ return None
+
+ monitoring = 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)
+ monitoring = dict_merge(default_values, monitoring)
+
+ monitoring['custom_scripts_dir'] = custom_scripts_dir
+ monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
+ monitoring['nft_chains'] = get_nft_filter_chains()
+
+ return monitoring
+
+def verify(monitoring):
+ # bail out early - looks like removal from running config
+ if not monitoring:
+ return None
+
+ if 'authentication' not in monitoring or \
+ 'organization' not in monitoring['authentication'] or \
+ 'token' not in monitoring['authentication']:
+ raise ConfigError(f'Authentication "organization and token" are mandatory!')
+
+ if 'url' not in monitoring:
+ raise ConfigError(f'Monitoring "url" is mandatory!')
+
+ return None
+
+def generate(monitoring):
+ if not monitoring:
+ # Delete config and systemd files
+ config_files = [config_telegraf, systemd_telegraf_service, systemd_override, syslog_telegraf]
+ for file in config_files:
+ if os.path.isfile(file):
+ os.unlink(file)
+
+ # Delete old directories
+ if os.path.isdir(cache_dir):
+ rmtree(cache_dir, ignore_errors=True)
+
+ return None
+
+ # Create telegraf cache dir
+ if not os.path.exists(cache_dir):
+ os.makedirs(cache_dir)
+
+ chown(cache_dir, 'telegraf', 'telegraf')
+
+ # Create systemd override dir
+ if not os.path.exists(systemd_telegraf_override_dir):
+ os.mkdir(systemd_telegraf_override_dir)
+
+ # Create custome scripts dir
+ if not os.path.exists(custom_scripts_dir):
+ os.mkdir(custom_scripts_dir)
+
+ # Render telegraf configuration and systemd override
+ render(config_telegraf, 'monitoring/telegraf.tmpl', monitoring)
+ render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.tmpl', monitoring)
+ render(systemd_override, 'monitoring/override.conf.tmpl', monitoring, permission=0o640)
+ render(syslog_telegraf, 'monitoring/syslog_telegraf.tmpl', monitoring)
+
+ chown(base_dir, 'telegraf', 'telegraf')
+
+ return None
+
+def apply(monitoring):
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+ if monitoring:
+ call('systemctl restart vyos-telegraf.service')
+ else:
+ call('systemctl stop vyos-telegraf.service')
+ # Telegraf include custom rsyslog config changes
+ call('systemctl restart rsyslog')
+
+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_upnp.py b/src/conf_mode/service_upnp.py
new file mode 100755
index 000000000..d21b31990
--- /dev/null
+++ b/src/conf_mode/service_upnp.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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 sys import exit
+import uuid
+import netifaces
+from ipaddress import IPv4Network
+from ipaddress import IPv6Network
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configdict import get_interface_dict
+from vyos.configverify import verify_vrf
+from vyos.util 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()
+
+config_file = r'/run/upnp/miniupnp.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['service', 'upnp']
+ upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ 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])
+
+ uuidgen = uuid.uuid1()
+ upnpd.update({'uuid': uuidgen})
+
+ return upnpd
+
+def get_all_interface_addr(prefix, filter_dev, filter_family):
+ list_addr = []
+ interfaces = netifaces.interfaces()
+
+ for interface in interfaces:
+ if filter_dev and interface in filter_dev:
+ continue
+ addrs = netifaces.ifaddresses(interface)
+ if netifaces.AF_INET in addrs.keys():
+ if netifaces.AF_INET in filter_family:
+ for addr in addrs[netifaces.AF_INET]:
+ if prefix:
+ # we need to manually assemble a list of IPv4 address/prefix
+ prefix = '/' + \
+ str(IPv4Network('0.0.0.0/' + addr['netmask']).prefixlen)
+ list_addr.append(addr['addr'] + prefix)
+ else:
+ list_addr.append(addr['addr'])
+ if netifaces.AF_INET6 in addrs.keys():
+ if netifaces.AF_INET6 in filter_family:
+ for addr in addrs[netifaces.AF_INET6]:
+ if prefix:
+ # we need to manually assemble a list of IPv4 address/prefix
+ bits = bin(int(addr['netmask'].replace(':', '').split('/')[0], 16)).count('1')
+ prefix = '/' + str(bits)
+ list_addr.append(addr['addr'] + prefix)
+ else:
+ list_addr.append(addr['addr'])
+
+ return list_addr
+
+def verify(upnpd):
+ if not upnpd:
+ return None
+
+ if 'wan_interface' not in upnpd:
+ raise ConfigError('To enable UPNP, you must have the "wan-interface" option!')
+
+ if 'rule' in upnpd:
+ for rule, rule_config in upnpd['rule'].items():
+ for option in ['external_port_range', 'internal_port_range', 'ip', 'action']:
+ if option not in rule_config:
+ tmp = option.replace('_', '-')
+ raise ConfigError(f'Every UPNP rule requires "{tmp}" to be set!')
+
+ if 'stun' in upnpd:
+ for option in ['host', 'port']:
+ if option not in upnpd['stun']:
+ raise ConfigError(f'A UPNP stun support must have an "{option}" option!')
+
+ # Check the validity of the IP address
+ listen_dev = []
+ system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6])
+ system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6])
+ for listen_if_or_addr in upnpd['listen']:
+ if listen_if_or_addr not in netifaces.interfaces():
+ listen_dev.append(listen_if_or_addr)
+ if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()):
+ if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast:
+ raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!')
+ if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast:
+ raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!')
+
+ system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6])
+ system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6])
+ for listen_if_or_addr in upnpd['listen']:
+ if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast):
+ raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card')
+
+def generate(upnpd):
+ if not upnpd:
+ return None
+
+ if os.path.isfile(config_file):
+ os.unlink(config_file)
+
+ render(config_file, 'firewall/upnpd.conf.tmpl', upnpd)
+
+def apply(upnpd):
+ systemd_service_name = 'miniupnpd.service'
+ if not upnpd:
+ # Stop the UPNP service
+ call(f'systemctl stop {systemd_service_name}')
+ else:
+ # Start the UPNP service
+ call(f'systemctl restart {systemd_service_name}')
+
+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/zone_policy.py b/src/conf_mode/zone_policy.py
new file mode 100755
index 000000000..683f8f034
--- /dev/null
+++ b/src/conf_mode/zone_policy.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 json import loads
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render
+from vyos.util import cmd
+from vyos.util import dict_search_args
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+nftables_conf = '/run/nftables_zone.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['zone-policy']
+ zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if zone_policy:
+ zone_policy['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return zone_policy
+
+def verify(zone_policy):
+ # bail out early - looks like removal from running config
+ if not zone_policy:
+ return None
+
+ local_zone = False
+ interfaces = []
+
+ if 'zone' in zone_policy:
+ for zone, zone_conf in zone_policy['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 interfaces]
+
+ if found_duplicates:
+ raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
+
+ 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(zone_policy, '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(zone_policy, '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 zone_policy['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:
+ if 'name' not in zone_policy['firewall']:
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ if not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+
+ v6_name = dict_search_args(from_conf, 'firewall', 'v6_name')
+ if v6_name:
+ if 'ipv6_name' not in zone_policy['firewall']:
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
+ if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+
+ return None
+
+def has_ipv4_fw(zone_conf):
+ if 'from' not in zone_conf:
+ return False
+ zone_from = zone_conf['from']
+ return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'name')])
+
+def has_ipv6_fw(zone_conf):
+ if 'from' not in zone_conf:
+ return False
+ zone_from = zone_conf['from']
+ return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'ipv6_name')])
+
+def get_local_from(zone_policy, local_zone_name):
+ # Get all zone firewall names from the local zone
+ out = {}
+ for zone, zone_conf in zone_policy['zone'].items():
+ if zone == local_zone_name:
+ continue
+ if 'from' not in zone_conf:
+ continue
+ if local_zone_name in zone_conf['from']:
+ out[zone] = zone_conf['from'][local_zone_name]
+ return out
+
+def cleanup_commands():
+ commands = []
+ for table in ['ip filter', 'ip6 filter']:
+ json_str = cmd(f'nft -j list table {table}')
+ obj = loads(json_str)
+ if 'nftables' not in obj:
+ continue
+ for item in obj['nftables']:
+ if 'rule' in item:
+ chain = item['rule']['chain']
+ handle = item['rule']['handle']
+ if 'expr' not in item['rule']:
+ continue
+ for expr in item['rule']['expr']:
+ target = dict_search_args(expr, 'jump', 'target')
+ if not target:
+ continue
+ if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"):
+ commands.append(f'delete rule {table} {chain} handle {handle}')
+ for item in obj['nftables']:
+ if 'chain' in item:
+ if item['chain']['name'].startswith("VZONE"):
+ chain = item['chain']['name']
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+
+def generate(zone_policy):
+ data = zone_policy or {}
+
+ if os.path.exists(nftables_conf): # Check to see if we've run before
+ data['cleanup_commands'] = cleanup_commands()
+
+ if 'zone' in data:
+ for zone, zone_conf in data['zone'].items():
+ zone_conf['ipv4'] = has_ipv4_fw(zone_conf)
+ zone_conf['ipv6'] = has_ipv6_fw(zone_conf)
+
+ if 'local_zone' in zone_conf:
+ zone_conf['from_local'] = get_local_from(data, zone)
+
+ render(nftables_conf, 'zone_policy/nftables.tmpl', data)
+ return None
+
+def apply(zone_policy):
+ install_result = run(f'nft -f {nftables_conf}')
+ if install_result != 0:
+ raise ConfigError('Failed to apply zone-policy')
+
+ return None
+
+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/etc/systemd/system/keepalived.service.d/override.conf b/src/etc/systemd/system/keepalived.service.d/override.conf
deleted file mode 100644
index 1c68913f2..000000000
--- a/src/etc/systemd/system/keepalived.service.d/override.conf
+++ /dev/null
@@ -1,13 +0,0 @@
-[Unit]
-ConditionPathExists=
-ConditionPathExists=/run/keepalived/keepalived.conf
-After=
-After=vyos-router.service
-
-[Service]
-KillMode=process
-EnvironmentFile=
-ExecStart=
-ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp
-PIDFile=
-PIDFile=/run/keepalived/keepalived.pid
diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
new file mode 100755
index 000000000..bf4bfd05d
--- /dev/null
+++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+import json
+import re
+import time
+
+from vyos.util import cmd
+
+
+def get_nft_filter_chains():
+ """
+ Get list of nft chains for table filter
+ """
+ nft = cmd('/usr/sbin/nft --json list table ip filter')
+ nft = json.loads(nft)
+ chain_list = []
+
+ for output in nft['nftables']:
+ if 'chain' in output:
+ chain = output['chain']['name']
+ chain_list.append(chain)
+
+ return chain_list
+
+
+def get_nftables_details(name):
+ """
+ Get dict, counters packets and bytes for chain
+ """
+ command = f'/usr/sbin/nft list chain ip filter {name}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ # Trick to remove 'NAME_' from chain name in the comment
+ # It was added to any chain T4218
+ # counter packets 0 bytes 0 return comment "FOO default-action accept"
+ comment_name = name.replace("NAME_", "")
+ out = {}
+ for line in results.split('\n'):
+ comment_search = re.search(rf'{comment_name}[\- ](\d+|default-action)', line)
+ if not comment_search:
+ continue
+
+ rule = {}
+ rule_id = comment_search[1]
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[rule_id] = rule
+ return out
+
+
+def get_nft_telegraf(name):
+ """
+ Get data for telegraf in influxDB format
+ """
+ for rule, rule_config in get_nftables_details(name).items():
+ print(f'nftables,table=filter,chain={name},'
+ f'ruleid={rule} '
+ f'pkts={rule_config["packets"]}i,'
+ f'bytes={rule_config["bytes"]}i '
+ f'{str(int(time.time()))}000000000')
+
+
+chains = get_nft_filter_chains()
+
+for chain in chains:
+ get_nft_telegraf(chain)
diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
new file mode 100755
index 000000000..0c7474156
--- /dev/null
+++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+
+import time
+
+def get_interfaces(type='', vlan=True):
+ """
+ Get interfaces:
+ ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
+ """
+ interfaces = []
+ ifaces = Section.interfaces(type)
+ for iface in ifaces:
+ if vlan == False and '.' in iface:
+ continue
+ interfaces.append(iface)
+
+ return interfaces
+
+def get_interface_addresses(iface, link_local_v6=False):
+ """
+ Get IP and IPv6 addresses from interface in one string
+ By default don't get IPv6 link-local addresses
+ If interface doesn't have address, return "-"
+ """
+ addresses = []
+ addrs = Interface(iface).get_addr()
+
+ for addr in addrs:
+ if link_local_v6 == False:
+ if addr.startswith('fe80::'):
+ continue
+ addresses.append(addr)
+
+ if not addresses:
+ return "-"
+
+ return (" ".join(addresses))
+
+def get_interface_description(iface):
+ """
+ Get interface description
+ If none return "empty"
+ """
+ description = Interface(iface).get_alias()
+
+ if not description:
+ return "empty"
+
+ return description
+
+def get_interface_admin_state(iface):
+ """
+ Interface administrative state
+ up => 0, down => 2
+ """
+ state = Interface(iface).get_admin_state()
+ if state == 'up':
+ admin_state = 0
+ if state == 'down':
+ admin_state = 2
+
+ return admin_state
+
+def get_interface_oper_state(iface):
+ """
+ Interface operational state
+ up => 0, down => 1
+ """
+ state = Interface(iface).operational.get_state()
+ if state == 'down':
+ oper_state = 1
+ else:
+ oper_state = 0
+
+ return oper_state
+
+interfaces = get_interfaces()
+
+for iface in interfaces:
+ print(f'show_interfaces,interface={iface} '
+ f'ip_addresses="{get_interface_addresses(iface)}",'
+ f'state={get_interface_admin_state(iface)}i,'
+ f'link={get_interface_oper_state(iface)}i,'
+ f'description="{get_interface_description(iface)}" '
+ f'{str(int(time.time()))}000000000')
diff --git a/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py
new file mode 100755
index 000000000..df4eed131
--- /dev/null
+++ b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 time
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import is_systemd_service_running, process_named_running
+
+# Availible services and prouceses
+# 1 - service
+# 2 - process
+services = {
+ "protocols bgp" : "bgpd",
+ "protocols ospf" : "ospfd",
+ "protocols ospfv3" : "ospf6d",
+ "protocols rip" : "ripd",
+ "protocols ripng" : "ripngd",
+ "protocols isis" : "isisd",
+ "service pppoe" : "accel-ppp@pppoe.service",
+ "vpn l2tp remote-access" : "accel-ppp@l2tp.service",
+ "vpn pptp remote-access" : "accel-ppp@pptp.service",
+ "vpn sstp" : "accel-ppp@sstp.service",
+ "vpn ipsec" : "charon"
+}
+
+# Configured services
+conf_services = {
+ 'zebra' : 0,
+ 'staticd' : 0,
+}
+# Get configured service and create list to check if process running
+config = ConfigTreeQuery()
+for service in services:
+ if config.exists(service):
+ conf_services[services[service]] = 0
+
+for conf_service in conf_services:
+ status = 0
+ if ".service" in conf_service:
+ # Check systemd service
+ if is_systemd_service_running(conf_service):
+ status = 1
+ else:
+ # Check process
+ if process_named_running(conf_service):
+ status = 1
+ print(f'vyos_services,service="{conf_service}" '
+ f'status={str(status)}i {str(int(time.time()))}000000000')
diff --git a/src/helpers/strip-private.py b/src/helpers/strip-private.py
index e4e1fe11d..eb584edaf 100755
--- a/src/helpers/strip-private.py
+++ b/src/helpers/strip-private.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -111,6 +111,10 @@ if __name__ == "__main__":
(True, re.compile(r'public-keys \S+'), 'public-keys xxxx@xxx.xxx'),
(True, re.compile(r'type \'ssh-(rsa|dss)\''), 'type ssh-xxx'),
(True, re.compile(r' key \S+'), ' key xxxxxx'),
+ # Strip bucket
+ (True, re.compile(r' bucket \S+'), ' bucket xxxxxx'),
+ # Strip tokens
+ (True, re.compile(r' token \S+'), ' token xxxxxx'),
# Strip OpenVPN secrets
(True, re.compile(r'(shared-secret-key-file|ca-cert-file|cert-file|dh-file|key-file|client) (\S+)'), r'\1 xxxxxx'),
# Strip IPSEC secrets
@@ -123,8 +127,8 @@ if __name__ == "__main__":
# Strip MAC addresses
(args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'xx:xx:xx:xx:xx:\2'),
- # Strip host-name, domain-name, and domain-search
- (args.hostname, re.compile(r'(host-name|domain-name|domain-search) \S+'), r'\1 xxxxxx'),
+ # Strip host-name, domain-name, domain-search and url
+ (args.hostname, re.compile(r'(host-name|domain-name|domain-search|url) \S+'), r'\1 xxxxxx'),
# Strip user-names
(args.username, re.compile(r'(user|username|user-id) \S+'), r'\1 xxxxxx'),
diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name
index afeef8f2d..1798e92db 100755
--- a/src/helpers/vyos_net_name
+++ b/src/helpers/vyos_net_name
@@ -20,12 +20,14 @@ import os
import re
import time
import logging
+import tempfile
import threading
from sys import argv
from vyos.configtree import ConfigTree
from vyos.defaults import directories
from vyos.util import cmd, boot_configuration_complete
+from vyos.migrator import VirtualMigrator
vyos_udev_dir = directories['vyos_udev_dir']
vyos_log_dir = '/run/udev/log'
@@ -139,14 +141,20 @@ def get_configfile_interfaces() -> dict:
try:
config = ConfigTree(config_file)
except Exception:
- logging.debug(f"updating component version string syntax")
try:
- # this will update the component version string in place, for
- # updates 1.2 --> 1.3/1.4
- os.system(f'/usr/libexec/vyos/run-config-migration.py {config_path} --virtual --set-vintage=vyos')
- with open(config_path) as f:
- config_file = f.read()
+ logging.debug(f"updating component version string syntax")
+ # this will update the component version string syntax,
+ # required for updates 1.2 --> 1.3/1.4
+ with tempfile.NamedTemporaryFile() as fp:
+ with open(fp.name, 'w') as fd:
+ fd.write(config_file)
+ virtual_migration = VirtualMigrator(fp.name)
+ virtual_migration.run()
+ with open(fp.name) as fd:
+ config_file = fd.read()
+
config = ConfigTree(config_file)
+
except Exception as e:
logging.critical(f"ConfigTree error: {e}")
@@ -246,4 +254,3 @@ if not boot_configuration_complete():
else:
logging.debug("boot configuration complete")
lock.release()
-
diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2
index 4c6d5ceb8..e2d3fcd33 100755
--- a/src/migration-scripts/bgp/1-to-2
+++ b/src/migration-scripts/bgp/1-to-2
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -20,7 +20,6 @@ from sys import argv
from sys import exit
from vyos.configtree import ConfigTree
-from vyos.template import is_ipv4
if (len(argv) < 1):
print("Must specify file name!")
@@ -51,23 +50,21 @@ if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']):
# Check if the "default" node is now empty, if so - remove it
if len(config.list_nodes(base + ['parameters'])) == 0:
config.delete(base + ['parameters'])
+else:
+ # As we now install a new default option into BGP we need to migrate all
+ # existing BGP neighbors and restore the old behavior
+ if config.exists(base + ['neighbor']):
+ for neighbor in config.list_nodes(base + ['neighbor']):
+ peer_group = base + ['neighbor', neighbor, 'peer-group']
+ if config.exists(peer_group):
+ peer_group_name = config.return_value(peer_group)
+ # peer group enables old behavior for neighbor - bail out
+ if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']):
+ continue
- exit(0)
-
-# As we now install a new default option into BGP we need to migrate all
-# existing BGP neighbors and restore the old behavior
-if config.exists(base + ['neighbor']):
- for neighbor in config.list_nodes(base + ['neighbor']):
- peer_group = base + ['neighbor', neighbor, 'peer-group']
- if config.exists(peer_group):
- peer_group_name = config.return_value(peer_group)
- # peer group enables old behavior for neighbor - bail out
- if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']):
- continue
-
- afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast']
- if not config.exists(afi_ipv4):
- config.set(afi_ipv4)
+ afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast']
+ if not config.exists(afi_ipv4):
+ config.set(afi_ipv4)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2
index ba10c26f2..a8c930be7 100755
--- a/src/migration-scripts/dns-forwarding/1-to-2
+++ b/src/migration-scripts/dns-forwarding/1-to-2
@@ -16,7 +16,7 @@
#
# This migration script will remove the deprecated 'listen-on' statement
-# from the dns forwarding service and will add the corresponding
+# from the dns forwarding service and will add the corresponding
# listen-address nodes instead. This is required as PowerDNS can only listen
# on interface addresses and not on interface names.
@@ -37,53 +37,50 @@ with open(file_name, 'r') as f:
config = ConfigTree(config_file)
base = ['service', 'dns', 'forwarding']
-if not config.exists(base):
+if not config.exists(base + ['listen-on']):
# Nothing to do
exit(0)
-if config.exists(base + ['listen-on']):
- listen_intf = config.return_values(base + ['listen-on'])
- # Delete node with abandoned command
- config.delete(base + ['listen-on'])
+listen_intf = config.return_values(base + ['listen-on'])
+# Delete node with abandoned command
+config.delete(base + ['listen-on'])
- # retrieve interface addresses for every configured listen-on interface
- listen_addr = []
- for intf in listen_intf:
- # we need to evaluate the interface section before manipulating the 'intf' variable
- section = Interface.section(intf)
- if not section:
- raise ValueError(f'Invalid interface name {intf}')
+# retrieve interface addresses for every configured listen-on interface
+listen_addr = []
+for intf in listen_intf:
+ # we need to evaluate the interface section before manipulating the 'intf' variable
+ section = Interface.section(intf)
+ if not section:
+ raise ValueError(f'Invalid interface name {intf}')
- # we need to treat vif and vif-s interfaces differently,
- # both "real interfaces" use dots for vlan identifiers - those
- # need to be exchanged with vif and vif-s identifiers
- if intf.count('.') == 1:
- # this is a regular VLAN interface
- intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1]
- elif intf.count('.') == 2:
- # this is a QinQ VLAN interface
- intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
-
- # retrieve corresponding interface addresses in CIDR format
- # those need to be converted in pure IP addresses without network information
- path = ['interfaces', section, intf, 'address']
- try:
- for addr in config.return_values(path):
- listen_addr.append( ip_interface(addr).ip )
- except:
- # Some interface types do not use "address" option (e.g. OpenVPN)
- # and may not even have a fixed address
- print("Could not retrieve the address of the interface {} from the config".format(intf))
- print("You will need to update your DNS forwarding configuration manually")
-
- for addr in listen_addr:
- config.set(base + ['listen-address'], value=addr, replace=False)
+ # we need to treat vif and vif-s interfaces differently,
+ # both "real interfaces" use dots for vlan identifiers - those
+ # need to be exchanged with vif and vif-s identifiers
+ if intf.count('.') == 1:
+ # this is a regular VLAN interface
+ intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1]
+ elif intf.count('.') == 2:
+ # this is a QinQ VLAN interface
+ intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
+ # retrieve corresponding interface addresses in CIDR format
+ # those need to be converted in pure IP addresses without network information
+ path = ['interfaces', section, intf, 'address']
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)
+ for addr in config.return_values(path):
+ listen_addr.append( ip_interface(addr).ip )
+ except:
+ # Some interface types do not use "address" option (e.g. OpenVPN)
+ # and may not even have a fixed address
+ print("Could not retrieve the address of the interface {} from the config".format(intf))
+ print("You will need to update your DNS forwarding configuration manually")
-exit(0)
+for addr in listen_addr:
+ config.set(base + ['listen-address'], value=addr, replace=False)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
new file mode 100755
index 000000000..5f4cff90d
--- /dev/null
+++ b/src/migration-scripts/firewall/6-to-7
@@ -0,0 +1,226 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+# T2199: Remove unavailable nodes due to XML/Python implementation using nftables
+# monthdays: nftables does not have a monthdays equivalent
+# utc: nftables userspace uses localtime and calculates the UTC offset automatically
+# icmp/v6: migrate previously available `type-name` to valid type/code
+# T4178: Update tcp flags to use multi value node
+
+import re
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['firewall']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+icmp_remove = ['any']
+icmp_translations = {
+ 'ping': 'echo-request',
+ 'pong': 'echo-reply',
+ 'ttl-exceeded': 'time-exceeded',
+ # Network Unreachable
+ 'network-unreachable': [3, 0],
+ 'host-unreachable': [3, 1],
+ 'protocol-unreachable': [3, 2],
+ 'port-unreachable': [3, 3],
+ 'fragmentation-needed': [3, 4],
+ 'source-route-failed': [3, 5],
+ 'network-unknown': [3, 6],
+ 'host-unknown': [3, 7],
+ 'network-prohibited': [3, 9],
+ 'host-prohibited': [3, 10],
+ 'TOS-network-unreachable': [3, 11],
+ 'TOS-host-unreachable': [3, 12],
+ 'communication-prohibited': [3, 13],
+ 'host-precedence-violation': [3, 14],
+ 'precedence-cutoff': [3, 15],
+ # Redirect
+ 'network-redirect': [5, 0],
+ 'host-redirect': [5, 1],
+ 'TOS-network-redirect': [5, 2],
+ 'TOS host-redirect': [5, 3],
+ # Time Exceeded
+ 'ttl-zero-during-transit': [11, 0],
+ 'ttl-zero-during-reassembly': [11, 1],
+ # Parameter Problem
+ 'ip-header-bad': [12, 0],
+ 'required-option-missing': [12, 1]
+}
+
+icmpv6_remove = []
+icmpv6_translations = {
+ 'ping': 'echo-request',
+ 'pong': 'echo-reply',
+ # Destination Unreachable
+ 'no-route': [1, 0],
+ 'communication-prohibited': [1, 1],
+ 'address-unreachble': [1, 3],
+ 'port-unreachable': [1, 4],
+ # Redirect
+ 'redirect': 'nd-redirect',
+ # Time Exceeded
+ 'ttl-zero-during-transit': [3, 0],
+ 'ttl-zero-during-reassembly': [3, 1],
+ # Parameter Problem
+ 'bad-header': [4, 0],
+ 'unknown-header-type': [4, 1],
+ 'unknown-option': [4, 2]
+}
+
+if config.exists(base + ['name']):
+ for name in config.list_nodes(base + ['name']):
+ if not config.exists(base + ['name', name, 'rule']):
+ continue
+
+ for rule in config.list_nodes(base + ['name', name, 'rule']):
+ rule_recent = base + ['name', name, 'rule', rule, 'recent']
+ rule_time = base + ['name', name, 'rule', rule, 'time']
+ rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags']
+ rule_icmp = base + ['name', name, 'rule', rule, 'icmp']
+
+ if config.exists(rule_time + ['monthdays']):
+ config.delete(rule_time + ['monthdays'])
+
+ if config.exists(rule_time + ['utc']):
+ config.delete(rule_time + ['utc'])
+
+ if config.exists(rule_recent + ['time']):
+ tmp = int(config.return_value(rule_recent + ['time']))
+ unit = 'minute'
+ if tmp > 600:
+ unit = 'hour'
+ elif tmp < 10:
+ unit = 'second'
+ config.set(rule_recent + ['time'], value=unit)
+
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
+ if config.exists(rule_icmp + ['type-name']):
+ tmp = config.return_value(rule_icmp + ['type-name'])
+ if tmp in icmp_remove:
+ config.delete(rule_icmp + ['type-name'])
+ elif tmp in icmp_translations:
+ translate = icmp_translations[tmp]
+ if isinstance(translate, str):
+ config.set(rule_icmp + ['type-name'], value=translate)
+ elif isinstance(translate, list):
+ config.delete(rule_icmp + ['type-name'])
+ config.set(rule_icmp + ['type'], value=translate[0])
+ config.set(rule_icmp + ['code'], value=translate[1])
+
+ for src_dst in ['destination', 'source']:
+ pg_base = base + ['name', name, 'rule', rule, src_dst, 'group', 'port-group']
+ proto_base = base + ['name', name, 'rule', rule, 'protocol']
+ if config.exists(pg_base) and not config.exists(proto_base):
+ config.set(proto_base, value='tcp_udp')
+
+if config.exists(base + ['ipv6-name']):
+ for name in config.list_nodes(base + ['ipv6-name']):
+ if not config.exists(base + ['ipv6-name', name, 'rule']):
+ continue
+
+ for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
+ rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent']
+ rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']
+ rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags']
+ rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6']
+
+ if config.exists(rule_time + ['monthdays']):
+ config.delete(rule_time + ['monthdays'])
+
+ if config.exists(rule_time + ['utc']):
+ config.delete(rule_time + ['utc'])
+
+ if config.exists(rule_recent + ['time']):
+ tmp = int(config.return_value(rule_recent + ['time']))
+ unit = 'minute'
+ if tmp > 600:
+ unit = 'hour'
+ elif tmp < 10:
+ unit = 'second'
+ config.set(rule_recent + ['time'], value=unit)
+
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
+ if config.exists(base + ['ipv6-name', name, 'rule', rule, 'protocol']):
+ tmp = config.return_value(base + ['ipv6-name', name, 'rule', rule, 'protocol'])
+ if tmp == 'icmpv6':
+ config.set(base + ['ipv6-name', name, 'rule', rule, 'protocol'], value='ipv6-icmp')
+
+ if config.exists(rule_icmp + ['type']):
+ tmp = config.return_value(rule_icmp + ['type'])
+ type_code_match = re.match(r'^(\d+)/(\d+)$', tmp)
+
+ if type_code_match:
+ config.set(rule_icmp + ['type'], value=type_code_match[1])
+ config.set(rule_icmp + ['code'], value=type_code_match[2])
+ elif tmp in icmpv6_remove:
+ config.delete(rule_icmp + ['type'])
+ elif tmp in icmpv6_translations:
+ translate = icmpv6_translations[tmp]
+ if isinstance(translate, str):
+ config.delete(rule_icmp + ['type'])
+ config.set(rule_icmp + ['type-name'], value=translate)
+ elif isinstance(translate, list):
+ config.set(rule_icmp + ['type'], value=translate[0])
+ config.set(rule_icmp + ['code'], value=translate[1])
+ else:
+ config.rename(rule_icmp + ['type'], 'type-name')
+
+ for src_dst in ['destination', 'source']:
+ pg_base = base + ['ipv6-name', name, 'rule', rule, src_dst, 'group', 'port-group']
+ proto_base = base + ['ipv6-name', name, 'rule', rule, 'protocol']
+ if config.exists(pg_base) and not config.exists(proto_base):
+ config.set(proto_base, value='tcp_udp')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2
new file mode 100755
index 000000000..eebbf9d41
--- /dev/null
+++ b/src/migration-scripts/policy/1-to-2
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T4170: rename "policy ipv6-route" to "policy route6" to match common
+# IPv4/IPv6 schema
+# T4178: Update tcp flags to use multi value node
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['policy', 'ipv6-route']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+config.rename(base, 'route6')
+config.set_tag(['policy', 'route6'])
+
+for route in ['route', 'route6']:
+ route_path = ['policy', route]
+ if config.exists(route_path):
+ for name in config.list_nodes(route_path):
+ if config.exists(route_path + [name, 'rule']):
+ for rule in config.list_nodes(route_path + [name, 'rule']):
+ rule_tcp_flags = route_path + [name, 'rule', rule, 'tcp', 'flags']
+
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
+if config.exists(['interfaces']):
+ def if_policy_rename(config, path):
+ if config.exists(path + ['policy', 'ipv6-route']):
+ config.rename(path + ['policy', 'ipv6-route'], 'route6')
+
+ for if_type in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', if_type]):
+ if_path = ['interfaces', if_type, ifname]
+ if_policy_rename(config, if_path)
+
+ for vif_type in ['vif', 'vif-s']:
+ if config.exists(if_path + [vif_type]):
+ for vifname in config.list_nodes(if_path + [vif_type]):
+ if_policy_rename(config, if_path + [vif_type, vifname])
+
+ if config.exists(if_path + [vif_type, vifname, 'vif-c']):
+ for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']):
+ if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname])
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
new file mode 100755
index 000000000..3146fc357
--- /dev/null
+++ b/src/op_mode/firewall.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 argparse
+import ipaddress
+import json
+import re
+import tabulate
+
+from vyos.config import Config
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def get_firewall_interfaces(conf, firewall, name=None, ipv6=False):
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ directions = ['in', 'out', 'local']
+
+ def parse_if(ifname, if_conf):
+ if 'firewall' in if_conf:
+ for direction in directions:
+ if direction in if_conf['firewall']:
+ fw_conf = if_conf['firewall'][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)
+
+ for iftype in ['vif', 'vif_s', 'vif_c']:
+ if iftype in if_conf:
+ for vifname, vif_conf in if_conf[iftype].items():
+ parse_if(f'{ifname}.{vifname}', vif_conf)
+
+ for iftype, iftype_conf in interfaces.items():
+ for ifname, if_conf in iftype_conf.items():
+ parse_if(ifname, if_conf)
+
+ return firewall
+
+def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
+ config_path = ['firewall']
+ if name:
+ config_path += ['ipv6-name' if ipv6 else 'name', name]
+
+ 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(conf, firewall, name, ipv6)
+ return firewall
+
+def get_nftables_details(name, ipv6=False):
+ suffix = '6' if ipv6 else ''
+ name_prefix = 'NAME6_' if ipv6 else 'NAME_'
+ command = f'sudo nft list chain ip{suffix} filter {name_prefix}{name}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ out = {}
+ for line in results.split('\n'):
+ comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ if not comment_search:
+ continue
+
+ rule = {}
+ rule_id = comment_search[1]
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[rule_id] = rule
+ return out
+
+def output_firewall_name(name, name_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'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ row = [rule_id, rule_conf['action'], rule_conf['protocol'] if 'protocol' in rule_conf else 'all']
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ 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 details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ rows.append(row)
+
+ if rows:
+ 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):
+ 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'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0'
+ dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0'
+
+ row = [rule_id]
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ else:
+ row.append('0')
+ row.append('0')
+ row.append(rule_conf['action'])
+ row.append(source_addr)
+ row.append(dest_addr)
+ rows.append(row)
+
+ if 'default_action' in name_conf and not single_rule_id:
+ row = ['default']
+ if 'default-action' in details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ else:
+ row.append('0')
+ row.append('0')
+ row.append(name_conf['default_action'])
+ row.append('0.0.0.0/0') # Source
+ row.append('0.0.0.0/0') # Dest
+ rows.append(row)
+
+ if rows:
+ header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination']
+ print(tabulate.tabulate(rows, header) + '\n')
+
+def show_firewall():
+ print('Rulesets Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ 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 'ipv6_name' in firewall:
+ for name, name_conf in firewall['ipv6_name'].items():
+ output_firewall_name(name, name_conf, ipv6=True)
+
+def show_firewall_name(name, ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf, name, ipv6)
+ if firewall:
+ output_firewall_name(name, firewall, ipv6)
+
+def show_firewall_rule(name, rule_id, ipv6=False):
+ print('Rule Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf, name, ipv6)
+ if firewall:
+ output_firewall_name(name, firewall, ipv6, rule_id)
+
+def show_firewall_group(name=None):
+ conf = Config()
+ firewall = get_config_firewall(conf, interfaces=False)
+
+ if 'group' not in firewall:
+ return
+
+ 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:
+ 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']
+ rows = []
+
+ for group_type, group_type_conf in firewall['group'].items():
+ for group_name, group_conf in group_type_conf.items():
+ if name and name != group_name:
+ continue
+
+ references = find_references(group_type, group_name)
+ row = [group_name, group_type, '\n'.join(references) or 'N/A']
+ if 'address' in group_conf:
+ row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address)))
+ elif 'network' in group_conf:
+ row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network)))
+ elif 'mac_address' in group_conf:
+ row.append("\n".join(sorted(group_conf['mac_address'])))
+ elif 'port' in group_conf:
+ row.append("\n".join(sorted(group_conf['port'])))
+ else:
+ row.append('N/A')
+ rows.append(row)
+
+ if rows:
+ print('Firewall Groups\n')
+ print(tabulate.tabulate(rows, header))
+
+def show_summary():
+ print('Ruleset Summary')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ header = ['Ruleset Name', '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 '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 v6_out:
+ print('\nIPv6 name:\n')
+ print(tabulate.tabulate(v6_out, header) + '\n')
+
+ if v4_out:
+ print('\nIPv4 name:\n')
+ print(tabulate.tabulate(v4_out, header) + '\n')
+
+ show_firewall_group()
+
+def show_statistics():
+ print('Rulesets Statistics')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ 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 'ipv6_name' in firewall:
+ for name, name_conf in firewall['ipv6_name'].items():
+ output_firewall_name_statistics(name, name_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('--rule', help='Firewall Rule ID', required=False)
+ parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ if not args.rule:
+ show_firewall_name(args.name, args.ipv6)
+ else:
+ show_firewall_rule(args.name, args.rule, args.ipv6)
+ elif args.action == 'show_all':
+ show_firewall()
+ elif args.action == 'show_group':
+ show_firewall_group(args.name)
+ elif args.action == 'show_statistics':
+ show_statistics()
+ elif args.action == 'show_summary':
+ show_summary()
diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/monitor_bandwidth_test.sh
index 900223bca..a6ad0b42c 100755
--- a/src/op_mode/monitor_bandwidth_test.sh
+++ b/src/op_mode/monitor_bandwidth_test.sh
@@ -24,6 +24,9 @@ elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then
# Set address family to IPv6 when FQDN has at least one AAAA record
OPT="-V"
+else
+ # It's not IPv6, no option needed
+ OPT=""
fi
/usr/bin/iperf $OPT -c $1 $2
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
new file mode 100755
index 000000000..5be40082f
--- /dev/null
+++ b/src/op_mode/policy_route.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 argparse
+import re
+import tabulate
+
+from vyos.config import Config
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def get_policy_interfaces(conf, policy, name=None, ipv6=False):
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ routes = ['route', 'route6']
+
+ def parse_if(ifname, if_conf):
+ if 'policy' in if_conf:
+ for route in routes:
+ if route in if_conf['policy']:
+ route_name = if_conf['policy'][route]
+ name_str = f'({ifname},{route})'
+
+ if not name:
+ policy[route][route_name]['interface'].append(name_str)
+ elif not ipv6 and name == route_name:
+ policy['interface'].append(name_str)
+
+ for iftype in ['vif', 'vif_s', 'vif_c']:
+ if iftype in if_conf:
+ for vifname, vif_conf in if_conf[iftype].items():
+ parse_if(f'{ifname}.{vifname}', vif_conf)
+
+ for iftype, iftype_conf in interfaces.items():
+ for ifname, if_conf in iftype_conf.items():
+ parse_if(ifname, if_conf)
+
+def get_config_policy(conf, name=None, ipv6=False, interfaces=True):
+ config_path = ['policy']
+ if name:
+ config_path += ['route6' if ipv6 else 'route', name]
+
+ policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ if policy and interfaces:
+ if name:
+ policy['interface'] = []
+ else:
+ if 'route' in policy:
+ for route_name, route_conf in policy['route'].items():
+ route_conf['interface'] = []
+
+ if 'route6' in policy:
+ for route_name, route_conf in policy['route6'].items():
+ route_conf['interface'] = []
+
+ get_policy_interfaces(conf, policy, name, ipv6)
+
+ return policy
+
+def get_nftables_details(name, ipv6=False):
+ suffix = '6' if ipv6 else ''
+ command = f'sudo nft list chain ip{suffix} mangle VYOS_PBR{suffix}_{name}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ out = {}
+ for line in results.split('\n'):
+ comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ if not comment_search:
+ continue
+
+ rule = {}
+ rule_id = comment_search[1]
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[rule_id] = rule
+ return out
+
+def output_policy_route(name, route_conf, ipv6=False, single_rule_id=None):
+ ip_str = 'IPv6' if ipv6 else 'IPv4'
+ print(f'\n---------------------------------\n{ip_str} Policy Route "{name}"\n')
+
+ if route_conf['interface']:
+ print('Active on: {0}\n'.format(" ".join(route_conf['interface'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in route_conf:
+ for rule_id, rule_conf in route_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ action = rule_conf['action'] if 'action' in rule_conf else 'set'
+ protocol = rule_conf['protocol'] if 'protocol' in rule_conf else 'all'
+
+ row = [rule_id, action, protocol]
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ row.append(rule_details['conditions'])
+ rows.append(row)
+
+ if 'default_action' in route_conf and not single_rule_id:
+ row = ['default', route_conf['default_action'], 'all']
+ if 'default-action' in details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ rows.append(row)
+
+ if rows:
+ header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']
+ print(tabulate.tabulate(rows, header) + '\n')
+
+def show_policy(ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ policy = get_config_policy(conf)
+
+ if not policy:
+ return
+
+ if not ipv6 and 'route' in policy:
+ for route, route_conf in policy['route'].items():
+ output_policy_route(route, route_conf, ipv6=False)
+
+ if ipv6 and 'route6' in policy:
+ for route, route_conf in policy['route6'].items():
+ output_policy_route(route, route_conf, ipv6=True)
+
+def show_policy_name(name, ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ policy = get_config_policy(conf, name, ipv6)
+ if policy:
+ output_policy_route(name, policy, ipv6)
+
+def show_policy_rule(name, rule_id, ipv6=False):
+ print('Rule Information')
+
+ conf = Config()
+ policy = get_config_policy(conf, name, ipv6)
+ if policy:
+ output_policy_route(name, policy, ipv6, rule_id)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Action', required=False)
+ parser.add_argument('--name', help='Policy name', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--rule', help='Policy Rule ID', required=False)
+ parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ if not args.rule:
+ show_policy_name(args.name, args.ipv6)
+ else:
+ show_policy_rule(args.name, args.rule, args.ipv6)
+ elif args.action == 'show_all':
+ show_policy(args.ipv6)
diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py
new file mode 100755
index 000000000..377180dec
--- /dev/null
+++ b/src/op_mode/show_virtual_server.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import call
+
+def is_configured():
+ """ Check if high-availability virtual-server is configured """
+ config = ConfigTreeQuery()
+ if not config.exists(['high-availability', 'virtual-server']):
+ return False
+ return True
+
+if __name__ == '__main__':
+
+ if is_configured() == False:
+ print('Virtual server not configured!')
+ exit(0)
+
+ call('sudo ipvsadm --list --numeric')
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 2c1db20bf..dab146d28 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -23,6 +23,7 @@ import tabulate
import vyos.util
+from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.vrrp import VRRPError, VRRPNoData
@@ -35,7 +36,17 @@ group.add_argument("-d", "--data", action="store_true", help="Print detailed VRR
args = parser.parse_args()
+def is_configured():
+ """ Check if VRRP is configured """
+ config = ConfigTreeQuery()
+ if not config.exists(['high-availability', 'vrrp', 'group']):
+ return False
+ return True
+
# Exit early if VRRP is dead or not configured
+if is_configured() == False:
+ print('VRRP not configured!')
+ exit(0)
if not VRRP.is_running():
print('VRRP is not running')
sys.exit(0)
diff --git a/src/op_mode/zone_policy.py b/src/op_mode/zone_policy.py
new file mode 100755
index 000000000..7b43018c2
--- /dev/null
+++ b/src/op_mode/zone_policy.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 argparse
+import tabulate
+
+from vyos.config import Config
+from vyos.util import dict_search_args
+
+def get_config_zone(conf, name=None):
+ config_path = ['zone-policy']
+ if name:
+ config_path += ['zone', name]
+
+ zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ return zone_policy
+
+def output_zone_name(zone, zone_conf):
+ print(f'\n---------------------------------\nZone: "{zone}"\n')
+
+ interfaces = ', '.join(zone_conf['interface']) if 'interface' in zone_conf else ''
+ if 'local_zone' in zone_conf:
+ interfaces = 'LOCAL'
+
+ print(f'Interfaces: {interfaces}\n')
+
+ header = ['From Zone', 'Firewall']
+ rows = []
+
+ if 'from' in zone_conf:
+ for from_name, from_conf in zone_conf['from'].items():
+ row = [from_name]
+ v4_name = dict_search_args(from_conf, 'firewall', 'name')
+ v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+
+ if v4_name:
+ rows.append(row + [v4_name])
+
+ if v6_name:
+ rows.append(row + [f'{v6_name} [IPv6]'])
+
+ if rows:
+ print('From Zones:\n')
+ print(tabulate.tabulate(rows, header))
+
+def show_zone_policy(zone):
+ conf = Config()
+ zone_policy = get_config_zone(conf, zone)
+
+ if not zone_policy:
+ return
+
+ if 'zone' in zone_policy:
+ for zone, zone_conf in zone_policy['zone'].items():
+ output_zone_name(zone, zone_conf)
+ elif zone:
+ output_zone_name(zone, zone_policy)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Action', required=False)
+ parser.add_argument('--name', help='Zone name', required=False, action='store', nargs='?', default='')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ show_zone_policy(args.name)
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index b1fe7e43f..a8df232ae 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -71,7 +71,8 @@ class KeepalivedFifo:
# Read VRRP configuration directly from CLI
self.vrrp_config_dict = conf.get_config_dict(base,
- key_mangling=('-', '_'), get_first_key=True)
+ key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
logger.debug(f'Loaded configuration: {self.vrrp_config_dict}')
except Exception as err:
diff --git a/src/systemd/keepalived.service b/src/systemd/keepalived.service
new file mode 100644
index 000000000..a462d8614
--- /dev/null
+++ b/src/systemd/keepalived.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Keepalive Daemon (LVS and VRRP)
+After=vyos-router.service
+# Only start if there is a configuration file
+ConditionFileNotEmpty=/run/keepalived/keepalived.conf
+
+[Service]
+KillMode=process
+Type=simple
+# Read configuration variable file if it is present
+ExecStart=/usr/sbin/keepalived --use-file /run/keepalived/keepalived.conf --pid /run/keepalived/keepalived.pid --dont-fork --snmp
+ExecReload=/bin/kill -HUP $MAINPID
+PIDFile=/run/keepalived/keepalived.pid
diff --git a/src/systemd/miniupnpd.service b/src/systemd/miniupnpd.service
new file mode 100644
index 000000000..51cb2eed8
--- /dev/null
+++ b/src/systemd/miniupnpd.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=UPnP service
+ConditionPathExists=/run/upnp/miniupnp.conf
+After=vyos-router.service
+StartLimitIntervalSec=0
+
+[Service]
+WorkingDirectory=/run/upnp
+Type=simple
+ExecStart=/usr/sbin/miniupnpd -d -f /run/upnp/miniupnp.conf
+PrivateTmp=yes
+PIDFile=/run/miniupnpd.pid
+Restart=on-failure
diff --git a/src/tests/test_validate.py b/src/tests/test_validate.py
index b43dbd97e..68a257d25 100644
--- a/src/tests/test_validate.py
+++ b/src/tests/test_validate.py
@@ -30,8 +30,12 @@ class TestVyOSValidate(TestCase):
self.assertFalse(vyos.validate.is_ipv6_link_local('169.254.0.1'))
self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::'))
self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::affe:1'))
+ self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::affe:1%eth0'))
self.assertFalse(vyos.validate.is_ipv6_link_local('2001:db8::'))
+ self.assertFalse(vyos.validate.is_ipv6_link_local('2001:db8::%eth0'))
self.assertFalse(vyos.validate.is_ipv6_link_local('VyOS'))
+ self.assertFalse(vyos.validate.is_ipv6_link_local('::1'))
+ self.assertFalse(vyos.validate.is_ipv6_link_local('::1%lo'))
def test_is_ipv6_link_local(self):
self.assertTrue(vyos.validate.is_loopback_addr('127.0.0.1'))
diff --git a/src/validators/ip-address b/src/validators/ip-address
index 51fb72c85..11d6df09e 100755
--- a/src/validators/ip-address
+++ b/src/validators/ip-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr
index 987bf84ca..60d2ac295 100755
--- a/src/validators/ip-cidr
+++ b/src/validators/ip-cidr
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-cidr $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP CIDR"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-host b/src/validators/ip-host
index f2906e8cf..77c578fa2 100755
--- a/src/validators/ip-host
+++ b/src/validators/ip-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix
index e58aad395..e5a64fea8 100755
--- a/src/validators/ip-prefix
+++ b/src/validators/ip-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-protocol b/src/validators/ip-protocol
index 078f8e319..c4c882502 100755
--- a/src/validators/ip-protocol
+++ b/src/validators/ip-protocol
@@ -31,11 +31,12 @@ if __name__ == '__main__':
pattern = "!?\\b(all|ip|hopopt|icmp|igmp|ggp|ipencap|st|tcp|egp|igp|pup|udp|" \
"tcp_udp|hmp|xns-idp|rdp|iso-tp4|dccp|xtp|ddp|idpr-cmtp|ipv6|" \
- "ipv6-route|ipv6-frag|idrp|rsvp|gre|esp|ah|skip|ipv6-icmp|" \
+ "ipv6-route|ipv6-frag|idrp|rsvp|gre|esp|ah|skip|ipv6-icmp|icmpv6|" \
"ipv6-nonxt|ipv6-opts|rspf|vmtp|eigrp|ospf|ax.25|ipip|etherip|" \
"encap|99|pim|ipcomp|vrrp|l2tp|isis|sctp|fc|mobility-header|" \
"udplite|mpls-in-ip|manet|hip|shim6|wesp|rohc)\\b"
if re.match(pattern, input):
exit(0)
+ print(f'Error: {input} is not a valid IP protocol')
exit(1)
diff --git a/src/validators/ipv4 b/src/validators/ipv4
index 53face090..8676d5800 100755
--- a/src/validators/ipv4
+++ b/src/validators/ipv4
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4 $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not IPv4"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address
index 872a7645a..058db088b 100755
--- a/src/validators/ipv4-address
+++ b/src/validators/ipv4-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host
index f42feffa4..74b8c36a7 100755
--- a/src/validators/ipv4-host
+++ b/src/validators/ipv4-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast
index 5465c728d..3f28c51db 100755
--- a/src/validators/ipv4-multicast
+++ b/src/validators/ipv4-multicast
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 multicast address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix
index 8ec8a2c45..7e1e0e8dd 100755
--- a/src/validators/ipv4-prefix
+++ b/src/validators/ipv4-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-range b/src/validators/ipv4-range
index cc59039f1..6492bfc52 100755
--- a/src/validators/ipv4-range
+++ b/src/validators/ipv4-range
@@ -7,6 +7,11 @@ ip2dec () {
printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
}
+error_exit() {
+ echo "Error: $1 is not a valid IPv4 address range"
+ exit 1
+}
+
# Only run this if there is a hypen present in $1
if [[ "$1" =~ "-" ]]; then
# This only works with real bash (<<<) - split IP addresses into array with
@@ -15,21 +20,21 @@ if [[ "$1" =~ "-" ]]; then
ipaddrcheck --is-ipv4-single ${strarr[0]}
if [ $? -gt 0 ]; then
- exit 1
+ error_exit $1
fi
ipaddrcheck --is-ipv4-single ${strarr[1]}
if [ $? -gt 0 ]; then
- exit 1
+ error_exit $1
fi
start=$(ip2dec ${strarr[0]})
stop=$(ip2dec ${strarr[1]})
if [ $start -ge $stop ]; then
- exit 1
+ error_exit $1
fi
exit 0
fi
-exit 1
+error_exit $1
diff --git a/src/validators/ipv6 b/src/validators/ipv6
index f18d4a63e..4ae130eb5 100755
--- a/src/validators/ipv6
+++ b/src/validators/ipv6
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6 $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not IPv6"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address
index e5d68d756..1fca77668 100755
--- a/src/validators/ipv6-address
+++ b/src/validators/ipv6-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host
index f7a745077..7085809a9 100755
--- a/src/validators/ipv6-host
+++ b/src/validators/ipv6-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-link-local b/src/validators/ipv6-link-local
new file mode 100755
index 000000000..05e693b77
--- /dev/null
+++ b/src/validators/ipv6-link-local
@@ -0,0 +1,12 @@
+#!/usr/bin/python3
+
+import sys
+from vyos.validate import is_ipv6_link_local
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ addr = sys.argv[1]
+ if not is_ipv6_link_local(addr):
+ sys.exit(1)
+
+ sys.exit(0)
diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast
index 5afc437e5..5aa7d734a 100755
--- a/src/validators/ipv6-multicast
+++ b/src/validators/ipv6-multicast
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 multicast address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix
index e43616350..890dda723 100755
--- a/src/validators/ipv6-prefix
+++ b/src/validators/ipv6-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range
index 033b6461b..a3c401281 100755
--- a/src/validators/ipv6-range
+++ b/src/validators/ipv6-range
@@ -11,6 +11,7 @@ if __name__ == '__main__':
if re.search('([a-f0-9:]+:+)+[a-f0-9]+-([a-f0-9:]+:+)+[a-f0-9]+', ipv6_range):
for tmp in ipv6_range.split('-'):
if not is_ipv6(tmp):
+ print(f'Error: {ipv6_range} is not a valid IPv6 range')
sys.exit(1)
sys.exit(0)
diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall
new file mode 100755
index 000000000..70551f86d
--- /dev/null
+++ b/src/validators/mac-address-firewall
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# 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 re
+import sys
+
+pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$"
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ sys.exit(1)
+ if not re.match(pattern, sys.argv[1]):
+ sys.exit(1)
+ sys.exit(0)
diff --git a/src/validators/port-multi b/src/validators/port-multi
new file mode 100755
index 000000000..cef371563
--- /dev/null
+++ b/src/validators/port-multi
@@ -0,0 +1,45 @@
+#!/usr/bin/python3
+
+import sys
+import re
+
+from vyos.util import read_file
+
+services_file = '/etc/services'
+
+def get_services():
+ names = []
+ service_data = read_file(services_file, "")
+ for line in service_data.split("\n"):
+ if not line or line[0] == '#':
+ continue
+ names.append(line.split(None, 1)[0])
+ return names
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ ports = sys.argv[1].split(",")
+ services = get_services()
+
+ for port in ports:
+ if port and port[0] == '!':
+ port = port[1:]
+ if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port):
+ port_1, port_2 = port.split('-')
+ if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
+ print(f'Error: {port} is not a valid port range')
+ sys.exit(1)
+ if int(port_1) > int(port_2):
+ print(f'Error: {port} is not a valid port range')
+ sys.exit(1)
+ elif port.isnumeric():
+ if int(port) not in range(1, 65536):
+ print(f'Error: {port} is not a valid port')
+ sys.exit(1)
+ elif port not in services:
+ print(f'Error: {port} is not a valid service name')
+ sys.exit(1)
+ else:
+ sys.exit(2)
+
+ sys.exit(0)
diff --git a/src/validators/port-range b/src/validators/port-range
index abf0b09d5..5468000a7 100755
--- a/src/validators/port-range
+++ b/src/validators/port-range
@@ -3,16 +3,37 @@
import sys
import re
+from vyos.util import read_file
+
+services_file = '/etc/services'
+
+def get_services():
+ names = []
+ service_data = read_file(services_file, "")
+ for line in service_data.split("\n"):
+ if not line or line[0] == '#':
+ continue
+ names.append(line.split(None, 1)[0])
+ return names
+
+def error(port_range):
+ print(f'Error: {port_range} is not a valid port or port range')
+ sys.exit(1)
+
if __name__ == '__main__':
if len(sys.argv)>1:
port_range = sys.argv[1]
- if re.search('[0-9]{1,5}-[0-9]{1,5}', port_range):
- for tmp in port_range.split('-'):
- if int(tmp) not in range(1, 65535):
- sys.exit(1)
- else:
- if int(port_range) not in range(1, 65535):
- sys.exit(1)
+ if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port_range):
+ port_1, port_2 = port_range.split('-')
+ if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
+ error(port_range)
+ if int(port_1) > int(port_2):
+ error(port_range)
+ elif port_range.isnumeric() and int(port_range) not in range(1, 65536):
+ error(port_range)
+ elif not port_range.isnumeric() and port_range not in get_services():
+ print(f'Error: {port_range} is not a valid service name')
+ sys.exit(1)
else:
sys.exit(2)
diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag
new file mode 100755
index 000000000..1496b904a
--- /dev/null
+++ b/src/validators/tcp-flag
@@ -0,0 +1,17 @@
+#!/usr/bin/python3
+
+import sys
+import re
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ flag = sys.argv[1]
+ if flag and flag[0] == '!':
+ flag = flag[1:]
+ if flag not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh', 'ecn', 'cwr']:
+ print(f'Error: {flag} is not a valid TCP flag')
+ sys.exit(1)
+ else:
+ sys.exit(2)
+
+ sys.exit(0)
diff --git a/test-requirements.txt b/test-requirements.txt
index 9348520b5..a475e0a16 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,3 +3,4 @@ lxml
pylint
nose
coverage
+jinja2