summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-12-31 19:34:26 +0100
committerChristian Poessinger <christian@poessinger.com>2021-12-31 19:34:26 +0100
commit0091f6080181cc3836d70589d9a2f4a1c1cb11a8 (patch)
tree7ca1dbc816a2901b11d55c84c967592ed254aa0f
parentc5f118b3af482813a45c327ece29b5b41fd1ad9c (diff)
parent28b285b4791aece18fe1bbd76f3d555370545006 (diff)
downloadvyos-1x-0091f6080181cc3836d70589d9a2f4a1c1cb11a8.tar.gz
vyos-1x-0091f6080181cc3836d70589d9a2f4a1c1cb11a8.zip
Merge branch 'firewall' of https://github.com/sarthurdev/vyos-1x into current
* 'firewall' of https://github.com/sarthurdev/vyos-1x: zone_policy: T3873: Implement intra-zone-filtering policy: T2199: Migrate policy route op-mode to XML/Python policy: T2199: Migrate policy route to XML/Python zone-policy: T2199: Migrate zone-policy op-mode to XML/Python zone-policy: T2199: Migrate zone-policy to XML/Python firewall: T2199: Migrate firewall op-mode to XML/Python firewall: T2199: Migrate firewall to XML/Python
-rw-r--r--Makefile3
-rw-r--r--data/templates/firewall/nftables-nat.tmpl4
-rw-r--r--data/templates/firewall/nftables-policy.tmpl53
-rw-r--r--data/templates/firewall/nftables.tmpl292
-rw-r--r--data/templates/zone_policy/nftables.tmpl97
-rw-r--r--interface-definitions/firewall.xml.in75
-rw-r--r--interface-definitions/include/firewall/action.xml.i16
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i54
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv6.xml.i33
-rw-r--r--interface-definitions/include/firewall/source-destination-group.xml.i9
-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/policy/route-common-rule-ipv6.xml.i569
-rw-r--r--interface-definitions/include/policy/route-common-rule.xml.i418
-rw-r--r--interface-definitions/include/policy/route-rule-action.xml.i17
-rw-r--r--interface-definitions/interfaces-bonding.xml.in2
-rw-r--r--interface-definitions/interfaces-bridge.xml.in2
-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.in2
-rw-r--r--interface-definitions/interfaces-wireless.xml.in2
-rw-r--r--interface-definitions/interfaces-wwan.xml.in2
-rw-r--r--interface-definitions/policy-route.xml.in83
-rw-r--r--interface-definitions/zone-policy.xml.in143
-rw-r--r--op-mode-definitions/firewall.xml.in178
-rw-r--r--op-mode-definitions/policy-route.xml.in143
-rw-r--r--op-mode-definitions/zone-policy.xml.in24
-rw-r--r--python/vyos/configdiff.py30
-rw-r--r--python/vyos/firewall.py217
-rwxr-xr-xpython/vyos/ifconfig/interface.py45
-rw-r--r--python/vyos/template.py41
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py12
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py155
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py106
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_nhrp.py9
-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_zone_policy.py63
-rwxr-xr-xsrc/conf_mode/conntrack.py39
-rwxr-xr-xsrc/conf_mode/firewall-interface.py146
-rwxr-xr-xsrc/conf_mode/firewall.py267
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py89
-rwxr-xr-xsrc/conf_mode/nat.py13
-rwxr-xr-xsrc/conf_mode/nat66.py12
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py120
-rwxr-xr-xsrc/conf_mode/policy-route.py154
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py27
-rwxr-xr-xsrc/conf_mode/zone_policy.py196
-rwxr-xr-xsrc/migration-scripts/firewall/6-to-772
-rwxr-xr-xsrc/op_mode/firewall.py355
-rwxr-xr-xsrc/op_mode/policy_route.py189
-rwxr-xr-xsrc/op_mode/zone_policy.py81
-rwxr-xr-xsrc/validators/ip-protocol2
68 files changed, 4813 insertions, 230 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/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..aa6bb6fc1
--- /dev/null
+++ b/data/templates/firewall/nftables-policy.tmpl
@@ -0,0 +1,53 @@
+#!/usr/sbin/nft -f
+
+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 -%}
+{% 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 %}
+{% if conf.default_action is defined %}
+ counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
+{% else %}
+ counter return
+{% endif %}
+ }
+{% 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 ipv6_route is defined %}
+{% for route_text, conf in ipv6_route.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 %}
+{% if conf.default_action is defined %}
+ counter {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
+}
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl
new file mode 100644
index 000000000..34bd9b71e
--- /dev/null
+++ b/data/templates/firewall/nftables.tmpl
@@ -0,0 +1,292 @@
+#!/usr/sbin/nft -f
+
+{% if cleanup_commands is defined %}
+{% for command in cleanup_commands %}
+{{ command }}
+{% endfor %}
+{% endif %}
+
+{% 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.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 %}
+
+table ip filter {
+{% if first_install is defined %}
+ chain VYOS_FW_IN {
+ type filter hook forward priority 0; policy accept;
+ }
+ chain VYOS_FW_OUT {
+ type filter hook forward priority 1; 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 %}
+{% for name_text, conf in name.items() %}
+{% set default_log = 'log' if 'enable_default_log' in conf else '' %}
+ chain {{ 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) }}
+{% endfor %}
+{% endif %}
+{% if conf.default_action is defined %}
+ counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
+{% else %}
+ return
+{% endif %}
+ }
+{% 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_IN {
+ type filter hook forward priority 0; policy accept;
+ }
+ chain VYOS_FW6_OUT {
+ type filter hook forward priority 1; 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 %}
+{% for name_text, conf in ipv6_name.items() %}
+{% set default_log = 'log' if 'enable_default_log' in conf else '' %}
+ chain {{ 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') }}
+{% endfor %}
+{% endif %}
+{% if conf.default_action is defined %}
+ counter {{ default_log }} {{ conf.default_action | nft_action }} comment "{{ name_text }} default-action {{ conf.default_action }}"
+{% else %}
+ return
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
+{% if state_policy is defined %}
+ chain VYOS_STATE_POLICY6 {
+{% 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 %}
+}
+
+{% 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
+ 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
+ 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
+ }
+}
+
+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
+ notrack
+ }
+
+ chain OUTPUT {
+ type filter hook output priority -300; policy accept;
+ counter jump VYOS_CT_OUTPUT_HOOK
+ notrack
+ }
+
+ chain VYOS_CT_PREROUTING_HOOK {
+ return
+ }
+
+ chain VYOS_CT_OUTPUT_HOOK {
+ return
+ }
+}
+{% endif %}
diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl
new file mode 100644
index 000000000..21230c688
--- /dev/null
+++ b/data/templates/zone_policy/nftables.tmpl
@@ -0,0 +1,97 @@
+#!/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 {{ 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 {{ 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) }}
+{% 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 {{ 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 {{ 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 {{ 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) }}
+{% 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 {{ 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_OUT 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_OUT oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }}
+{% endif %}
+{% endif %}
+{% endfor %}
+
+{% endif %}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 1f3c14208..78a48a522 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>
@@ -203,6 +206,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<tagNode name="ipv6-name">
<properties>
@@ -233,7 +237,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 +247,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>
@@ -300,7 +304,7 @@
<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>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>
@@ -462,63 +466,18 @@
<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)$</regex>
+ <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>
- <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">
- <properties>
- <help>KaZaA application packets</help>
- <valueless/>
- </properties>
- </leafNode>
- </children>
- </node>
</children>
</tagNode>
</children>
@@ -541,6 +500,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="ipv6-src-route">
<properties>
@@ -560,6 +520,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="log-martians">
<properties>
@@ -579,6 +540,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<tagNode name="name">
<properties>
@@ -678,6 +640,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<leafNode name="send-redirects">
<properties>
@@ -697,6 +660,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<leafNode name="source-validation">
<properties>
@@ -720,6 +684,7 @@
<regex>^(strict|loose|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
<node name="state-policy">
<properties>
@@ -773,6 +738,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>enable</defaultValue>
</leafNode>
<leafNode name="twa-hazards-protection">
<properties>
@@ -792,6 +758,7 @@
<regex>^(enable|disable)$</regex>
</constraint>
</properties>
+ <defaultValue>disable</defaultValue>
</leafNode>
</children>
</node>
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..415b6bf00 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"/>
@@ -121,7 +121,6 @@
<validator name="ip-protocol"/>
</constraint>
</properties>
- <defaultValue>all</defaultValue>
</leafNode>
<node name="recent">
<properties>
@@ -285,40 +284,65 @@
<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/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
new file mode 100644
index 000000000..7815b78d4
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
@@ -0,0 +1,33 @@
+<!-- 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>
+ <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..9a9bed0fe 100644
--- a/interface-definitions/include/firewall/source-destination-group.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group.xml.i
@@ -7,16 +7,25 @@
<leafNode name="address-group">
<properties>
<help>Group of addresses</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
</properties>
</leafNode>
<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/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..5dad6422b
--- /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="ipv6-route">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy ipv6-route</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..5ee80ae13
--- /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="ipv6-route">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy ipv6-route</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..06f025af1
--- /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="ipv6-route">
+ <properties>
+ <help>IPv6 policy route ruleset for interface</help>
+ <completionHelp>
+ <path>policy ipv6-route</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/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
new file mode 100644
index 000000000..2d6adcd1d
--- /dev/null
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -0,0 +1,569 @@
+<!-- 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>
+ </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>
+<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>
+<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..c4deefd2a
--- /dev/null
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -0,0 +1,418 @@
+<!-- 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>
+ </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>
+<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>
+<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/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..0856615be 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>
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..7a7c9c1d9 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>
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-route.xml.in b/interface-definitions/policy-route.xml.in
new file mode 100644
index 000000000..ed726d1e4
--- /dev/null
+++ b/interface-definitions/policy-route.xml.in
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="policy">
+ <children>
+ <tagNode name="ipv6-route" owner="${vyos_conf_scripts_dir}/policy-route.py">
+ <properties>
+ <help>IPv6 policy route rule set name</help>
+ <priority>201</priority>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ #include <include/firewall/name-default-log.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number (1-9999)</help>
+ </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</help>
+ <priority>201</priority>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ #include <include/firewall/name-default-log.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number (1-9999)</help>
+ </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/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in
new file mode 100644
index 000000000..dd64c7c16
--- /dev/null
+++ b/interface-definitions/zone-policy.xml.in
@@ -0,0 +1,143 @@
+<?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>
+ </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..84df67b3d
--- /dev/null
+++ b/op-mode-definitions/firewall.xml.in
@@ -0,0 +1,178 @@
+<?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>
+ <leafNode name="group">
+ <properties>
+ <help>Show firewall group</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show_group --name $4</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..c998e5487
--- /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="ipv6-route">
+ <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="ipv6-route">
+ <properties>
+ <help>Show IPv6 policy chains</help>
+ <completionHelp>
+ <path>policy ipv6-route</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 policy rules</help>
+ <completionHelp>
+ <path>policy ipv6-route ${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/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/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/firewall.py b/python/vyos/firewall.py
new file mode 100644
index 000000000..8b7402b7e
--- /dev/null
+++ b/python/vyos/firewall.py
@@ -0,0 +1,217 @@
+#!/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'])
+ output.append(f'ct state {{{states}}}')
+
+ if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
+ proto = rule_conf['protocol']
+ if proto == 'tcp_udp':
+ proto = '{tcp, udp}'
+ output.append('meta l4proto ' + proto)
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ prefix = side[0]
+ side_conf = rule_conf[side]
+
+ if 'address' in side_conf:
+ output.append(f'{ip_name} {prefix}addr {side_conf["address"]}')
+
+ 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"]
+
+ if isinstance(port, list):
+ port = ",".join(port)
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ output.append(f'{proto} {prefix}port {{{port}}}')
+
+ if 'group' in side_conf:
+ group = side_conf['group']
+ if 'address_group' in group:
+ group_name = group['address_group']
+ output.append(f'{ip_name} {prefix}addr $A{def_suffix}_{group_name}')
+ elif 'network_group' in group:
+ group_name = group['network_group']
+ output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}')
+ if 'port_group' in group:
+ proto = rule_conf['protocol']
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ output.append(f'{proto} {prefix}port $P_{group_name}')
+
+ if 'log' in rule_conf and rule_conf['log'] == 'enable':
+ output.append('log')
+
+ 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'meter {fw_name}_{rule_id} {{ ip saddr and 255.255.255.255 limit rate over {count}/{time} burst {count} packets }}')
+ # Waiting on input from nftables developers due to
+ # bug with above line and atomic chain flushing.
+
+ 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):
+ all_flags = []
+ include = []
+ for flag in flags.split(","):
+ if flag[0] == '!':
+ flag = flag[1:]
+ else:
+ include.append(flag)
+ all_flags.append(flag)
+ return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}'
+
+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(set_conf['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/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/template.py b/python/vyos/template.py
index f694b53e0..2987fcd0e 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()
@@ -503,3 +504,43 @@ 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_state_policy')
+def nft_state_policy(conf, state):
+ 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'
+
+ 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}'
+ return 'return'
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/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
new file mode 100755
index 000000000..1520020fd
--- /dev/null
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -0,0 +1,155 @@
+#!/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', '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'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump smoketest'],
+ ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'tcp dport { 53, 123 }', '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)
+
+ 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_udp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump smoketest'],
+ ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'return'],
+ ['meta l4proto { tcp, udp }', 'th 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)
+
+ 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 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)
+
+ 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_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
new file mode 100755
index 000000000..70a234187
--- /dev/null
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -0,0 +1,106 @@
+#!/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(['policy', 'route'])
+ self.cli_delete(['policy', 'ipv6-route'])
+ 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_udp'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
+
+ self.cli_commit()
+
+ mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id))
+
+ nftables_search = [
+ ['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
+ ['meta l4proto { tcp, udp }', 'th 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)
+
+ 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_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_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py
new file mode 100755
index 000000000..c0af6164b
--- /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 smoketest'],
+ ['oifname { "eth0" }', 'jump 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..c65ef9540 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
@@ -43,8 +46,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 +56,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'],
@@ -93,6 +94,17 @@ def generate(conntrack):
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 +115,17 @@ 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}')
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
new file mode 100755
index 000000000..3a17dc5a4
--- /dev/null
+++ b/src/conf_mode/firewall-interface.py
@@ -0,0 +1,146 @@
+#!/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()
+
+NFT_CHAINS = {
+ 'in': 'VYOS_FW_IN',
+ 'out': 'VYOS_FW_OUT',
+ 'local': 'VYOS_FW_LOCAL'
+}
+NFT6_CHAINS = {
+ 'in': 'VYOS_FW6_IN',
+ 'out': 'VYOS_FW6_OUT',
+ '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, 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:
+ run(f'nft delete rule {table} {chain} handle {handle_search[1]}')
+ return retval
+
+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, ifname, name)
+
+ if not rule_exists:
+ run(f'nft insert rule ip filter {chain} {if_prefix}ifname {ifname} counter jump {name}')
+ else:
+ cleanup_rule('ip filter', chain, ifname)
+
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+ if ipv6_name:
+ rule_exists = cleanup_rule('ip6 filter', ipv6_chain, ifname, ipv6_name)
+
+ if not rule_exists:
+ run(f'nft insert rule ip6 filter {ipv6_chain} {if_prefix}ifname {ifname} counter jump {ipv6_name}')
+ else:
+ cleanup_rule('ip6 filter', ipv6_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/firewall.py b/src/conf_mode/firewall.py
index 8e6ce5b14..5ac48c9ba 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -16,50 +16,295 @@
import os
+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()
+nftables_conf = '/run/nftables.conf'
-def get_config(config=None):
+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'}
+}
+
+preserve_chains = [
+ 'INPUT',
+ 'FORWARD',
+ 'OUTPUT',
+ 'VYOS_FW_IN',
+ 'VYOS_FW_OUT',
+ 'VYOS_FW_LOCAL',
+ 'VYOS_FW_OUTPUT',
+ 'VYOS_POST_FW',
+ 'VYOS_FRAG_MARK',
+ 'VYOS_FW6_IN',
+ 'VYOS_FW6_OUT',
+ 'VYOS_FW6_LOCAL',
+ 'VYOS_FW6_OUTPUT',
+ 'VYOS_POST_FW6',
+ 'VYOS_FRAG6_MARK'
+]
+
+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_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['nfirewall']
+ base = ['firewall']
+
+ if not conf.exists(base):
+ return {}
+
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['interfaces'] = get_firewall_interfaces(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')
+
+ 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
+
+ if not dict_search_args(firewall, 'group', fw_group):
+ error_group = fw_group.replace("_", "-")
+ raise ConfigError(f'Group defined in rule but {error_group} is not configured')
+
+ if group_name not in firewall['group'][fw_group]:
+ error_group = group.replace("_", "-")
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+
+ 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 '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 not dict_search_args(firewall, 'name', name):
+ raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and not dict_search_args(firewall, 'ipv6_name', ipv6_name):
+ raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+
return None
+def cleanup_commands(firewall):
+ 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 'chain' in item:
+ if item['chain']['name'] not in preserve_chains:
+ chain = item['chain']['name']
+ if table == 'ip filter' and dict_search_args(firewall, 'name', chain):
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain):
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+
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)
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 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:
+ for chain in ['INPUT', 'OUTPUT', 'FORWARD']:
+ cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
+ cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
+
+ apply_sysfs(firewall)
+
+ post_apply_trap(firewall)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 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/nat.py b/src/conf_mode/nat.py
index 59939d0fb..62fb9abad 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -93,7 +93,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 +105,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 +118,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
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index fb376a434..d0b6d27ac 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -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'
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
new file mode 100755
index 000000000..e81135a74
--- /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', 'ipv6_route']:
+ 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'oifname "{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 'ipv6_route' in if_policy:
+ name = 'VYOS_PBR6_' + if_policy['ipv6_route']
+ 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..d098be68d
--- /dev/null
+++ b/src/conf_mode/policy-route.py
@@ -0,0 +1,154 @@
+#!/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()
+
+mark_offset = 0x7FFFFFFF
+nftables_conf = '/run/nftables_policy.conf'
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['policy']
+
+ if not conf.exists(base + ['route']) and not conf.exists(base + ['ipv6-route']):
+ return None
+
+ policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return policy
+
+def verify(policy):
+ # bail out early - looks like removal from running config
+ if not policy:
+ return None
+
+ for route in ['route', 'ipv6_route']:
+ 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.items():
+ icmp = 'icmp' if route == 'route' 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.split(","):
+ raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
+ if 'tcp' in rule_conf:
+ if 'flags' in rule_conf['tcp']:
+ if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp':
+ raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP')
+
+
+ return None
+
+def generate(policy):
+ if not policy:
+ if os.path.exists(nftables_conf):
+ os.unlink(nftables_conf)
+ return None
+
+ if not os.path.exists(nftables_conf):
+ policy['first_install'] = True
+
+ render(nftables_conf, 'firewall/nftables-policy.tmpl', policy)
+ return None
+
+def apply_table_marks(policy):
+ for route in ['route', 'ipv6_route']:
+ 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():
+ set_table = dict_search_args(rule_conf, 'set', 'table')
+ if set_table:
+ if set_table == 'main':
+ set_table = '254'
+ table_mark = mark_offset - int(set_table)
+ cmd(f'ip rule add fwmark {table_mark} table {set_table}')
+
+def cleanup_table_marks():
+ json_rules = cmd('ip -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'ip rule del fwmark {fwmark} table {table}')
+
+def apply(policy):
+ if not policy or 'first_install' not in policy:
+ run(f'nft flush table ip mangle')
+ run(f'nft flush table ip6 mangle')
+
+ if not policy:
+ cleanup_table_marks()
+ return None
+
+ 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/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/zone_policy.py b/src/conf_mode/zone_policy.py
new file mode 100755
index 000000000..2535ea33b
--- /dev/null
+++ b/src/conf_mode/zone_policy.py
@@ -0,0 +1,196 @@
+#!/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():
+ 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 target and target.startswith("VZONE"):
+ 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 == 1:
+ 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/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
new file mode 100755
index 000000000..4a4097d56
--- /dev/null
+++ b/src/migration-scripts/firewall/6-to-7
@@ -0,0 +1,72 @@
+#!/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
+
+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)
+
+if config.exists(base + ['name']):
+ for name in config.list_nodes(base + ['name']):
+ if config.exists(base + ['name', name, 'rule']):
+ for rule in config.list_nodes(base + ['name', name, 'rule']):
+ rule_time = base + ['name', name, 'rule', rule, 'time']
+
+ 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(base + ['ipv6-name']):
+ for name in config.list_nodes(base + ['ipv6-name']):
+ if config.exists(base + ['ipv6-name', name, 'rule']):
+ for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
+ rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']
+
+ if config.exists(rule_time + ['monthdays']):
+ config.delete(rule_time + ['monthdays'])
+
+ if config.exists(rule_time + ['utc']):
+ config.delete(rule_time + ['utc'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
new file mode 100755
index 000000000..cf70890a6
--- /dev/null
+++ b/src/op_mode/firewall.py
@@ -0,0 +1,355 @@
+#!/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 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 ''
+ command = f'sudo nft list chain ip{suffix} filter {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, ', '.join(references)]
+ if 'address' in group_conf:
+ row.append(", ".join(group_conf['address']))
+ elif 'network' in group_conf:
+ row.append(", ".join(group_conf['network']))
+ elif 'port' in group_conf:
+ row.append(", ".join(group_conf['port']))
+ 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])
+
+ 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/policy_route.py b/src/op_mode/policy_route.py
new file mode 100755
index 000000000..e0b4ac514
--- /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', 'ipv6_route']
+
+ 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 += ['ipv6-route' 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 'ipv6_route' in policy:
+ for route_name, route_conf in policy['ipv6_route'].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 'ipv6_route' in policy:
+ for route, route_conf in policy['ipv6_route'].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/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/validators/ip-protocol b/src/validators/ip-protocol
index 078f8e319..7898fa6d0 100755
--- a/src/validators/ip-protocol
+++ b/src/validators/ip-protocol
@@ -31,7 +31,7 @@ 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"