summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/conntrack/nftables-ct.tmpl52
-rw-r--r--data/templates/firewall/nftables-defines.tmpl32
-rw-r--r--data/templates/firewall/nftables-policy.tmpl14
-rw-r--r--data/templates/firewall/nftables.tmpl50
-rw-r--r--data/templates/frr/ospfd.frr.tmpl3
-rw-r--r--data/templates/high-availability/keepalived.conf.tmpl18
-rw-r--r--data/templates/monitoring/telegraf.tmpl1
-rw-r--r--data/templates/nhrp/opennhrp.conf.tmpl2
-rw-r--r--data/templates/ntp/ntpd.conf.tmpl1
-rw-r--r--data/templates/zone_policy/nftables.tmpl6
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/firewall.xml.in31
-rw-r--r--interface-definitions/high-availability.xml.in37
-rw-r--r--interface-definitions/include/conntrack/log-common.xml.i20
-rw-r--r--interface-definitions/include/conntrack/timeout-common-protocols.xml.i172
-rw-r--r--interface-definitions/include/firewall/common-rule.xml.i31
-rw-r--r--interface-definitions/include/firewall/mac-group.xml.i10
-rw-r--r--interface-definitions/include/firewall/port.xml.i5
-rw-r--r--interface-definitions/include/firewall/source-destination-group-ipv6.xml.i1
-rw-r--r--interface-definitions/include/firewall/source-destination-group.xml.i1
-rw-r--r--interface-definitions/include/firewall/tcp-flags.xml.i119
-rw-r--r--interface-definitions/include/interface/interface-policy-vif-c.xml.i4
-rw-r--r--interface-definitions/include/interface/interface-policy-vif.xml.i4
-rw-r--r--interface-definitions/include/interface/interface-policy.xml.i4
-rw-r--r--interface-definitions/include/nat-port.xml.i2
-rw-r--r--interface-definitions/include/nat-rule.xml.i8
-rw-r--r--interface-definitions/include/ospf/protocol-common-config.xml.i12
-rw-r--r--interface-definitions/include/policy/route-common-rule-ipv6.xml.i24
-rw-r--r--interface-definitions/include/policy/route-common-rule.xml.i24
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in1
-rw-r--r--interface-definitions/policy-route.xml.in26
-rw-r--r--interface-definitions/policy.xml.in2
-rw-r--r--interface-definitions/system-conntrack.xml.in330
-rw-r--r--interface-definitions/vpn_ipsec.xml.in2
-rw-r--r--op-mode-definitions/policy-route.xml.in8
-rw-r--r--op-mode-definitions/show-virtual-server.xml.in13
-rw-r--r--python/vyos/firewall.py51
-rw-r--r--python/vyos/frr.py22
-rw-r--r--python/vyos/remote.py10
-rw-r--r--smoketest/configs/dialup-router-medium-vpn33
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py20
-rwxr-xr-xsmoketest/scripts/cli/test_ha_vrrp.py30
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py34
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py60
-rwxr-xr-xsrc/conf_mode/conntrack.py23
-rwxr-xr-xsrc/conf_mode/containers.py28
-rwxr-xr-xsrc/conf_mode/firewall.py94
-rwxr-xr-xsrc/conf_mode/nat.py16
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py8
-rwxr-xr-xsrc/conf_mode/policy-route.py193
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py25
-rwxr-xr-xsrc/etc/telegraf/custom_scripts/show_interfaces_input_filter.py123
-rwxr-xr-xsrc/helpers/strip-private.py10
-rwxr-xr-xsrc/migration-scripts/bgp/1-to-233
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/1-to-283
-rwxr-xr-xsrc/migration-scripts/firewall/6-to-721
-rwxr-xr-xsrc/migration-scripts/policy/1-to-286
-rwxr-xr-xsrc/op_mode/firewall.py15
-rwxr-xr-xsrc/op_mode/policy_route.py12
-rwxr-xr-xsrc/op_mode/show_virtual_server.py33
-rwxr-xr-xsrc/op_mode/vrrp.py13
-rwxr-xr-xsrc/validators/ip-address7
-rwxr-xr-xsrc/validators/ip-cidr7
-rwxr-xr-xsrc/validators/ip-host7
-rwxr-xr-xsrc/validators/ip-prefix7
-rwxr-xr-xsrc/validators/ip-protocol1
-rwxr-xr-xsrc/validators/ipv47
-rwxr-xr-xsrc/validators/ipv4-address7
-rwxr-xr-xsrc/validators/ipv4-host7
-rwxr-xr-xsrc/validators/ipv4-multicast7
-rwxr-xr-xsrc/validators/ipv4-prefix7
-rwxr-xr-xsrc/validators/ipv4-range13
-rwxr-xr-xsrc/validators/ipv67
-rwxr-xr-xsrc/validators/ipv6-address7
-rwxr-xr-xsrc/validators/ipv6-host7
-rwxr-xr-xsrc/validators/ipv6-multicast7
-rwxr-xr-xsrc/validators/ipv6-prefix7
-rwxr-xr-xsrc/validators/ipv6-range1
-rwxr-xr-xsrc/validators/mac-address-firewall27
-rwxr-xr-xsrc/validators/port-multi45
-rwxr-xr-xsrc/validators/port-range35
-rwxr-xr-xsrc/validators/tcp-flag17
-rw-r--r--test-requirements.txt1
83 files changed, 1808 insertions, 577 deletions
diff --git a/data/templates/conntrack/nftables-ct.tmpl b/data/templates/conntrack/nftables-ct.tmpl
new file mode 100644
index 000000000..c0fe5297d
--- /dev/null
+++ b/data/templates/conntrack/nftables-ct.tmpl
@@ -0,0 +1,52 @@
+#!/usr/sbin/nft -f
+
+{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %}
+{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %}
+
+# we first flush all chains and render the content from scratch - this makes
+# any delta check obsolete
+flush chain raw {{ nft_ct_ignore_name }}
+flush chain raw {{ nft_ct_timeout_name }}
+
+table raw {
+ chain {{ nft_ct_ignore_name }} {
+{% if ignore is defined and ignore.rule is defined and ignore.rule is not none %}
+{% for rule, rule_config in ignore.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }}
+{% set nft_command = '' %}
+{% if rule_config.inbound_interface is defined and rule_config.inbound_interface is not none %}
+{% set nft_command = nft_command ~ ' iifname ' ~ rule_config.inbound_interface %}
+{% endif %}
+{% if rule_config.protocol is defined and rule_config.protocol is not none %}
+{% set nft_command = nft_command ~ ' ip protocol ' ~ rule_config.protocol %}
+{% endif %}
+{% if rule_config.destination is defined and rule_config.destination is not none %}
+{% if rule_config.destination.address is defined and rule_config.destination.address is not none %}
+{% set nft_command = nft_command ~ ' ip daddr ' ~ rule_config.destination.address %}
+{% endif %}
+{% if rule_config.destination.port is defined and rule_config.destination.port is not none %}
+{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' dport { ' ~ rule_config.destination.port ~ ' }' %}
+{% endif %}
+{% endif %}
+{% if rule_config.source is defined and rule_config.source is not none %}
+{% if rule_config.source.address is defined and rule_config.source.address is not none %}
+{% set nft_command = nft_command ~ ' ip saddr ' ~ rule_config.source.address %}
+{% endif %}
+{% if rule_config.source.port is defined and rule_config.source.port is not none %}
+{% set nft_command = nft_command ~ ' ' ~ rule_config.protocol ~ ' sport { ' ~ rule_config.source.port ~ ' }' %}
+{% endif %}
+{% endif %}
+ {{ nft_command }} counter notrack comment ignore-{{ rule }}
+{% endfor %}
+{% endif %}
+ return
+ }
+ chain {{ nft_ct_timeout_name }} {
+{% if timeout is defined and timeout.custom is defined and timeout.custom.rule is defined and timeout.custom.rule is not none %}
+{% for rule, rule_config in timeout.custom.rule.items() %}
+ # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is defined and rule_config.description is not none }}
+{% endfor %}
+{% endif %}
+ return
+ }
+}
diff --git a/data/templates/firewall/nftables-defines.tmpl b/data/templates/firewall/nftables-defines.tmpl
new file mode 100644
index 000000000..d9eb7c199
--- /dev/null
+++ b/data/templates/firewall/nftables-defines.tmpl
@@ -0,0 +1,32 @@
+{% if group is defined %}
+{% if group.address_group is defined %}
+{% for group_name, group_conf in group.address_group.items() %}
+define A_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.ipv6_address_group is defined %}
+{% for group_name, group_conf in group.ipv6_address_group.items() %}
+define A6_{{ group_name }} = { {{ group_conf.address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.mac_group is defined %}
+{% for group_name, group_conf in group.mac_group.items() %}
+define M_{{ group_name }} = { {{ group_conf.mac_address | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.network_group is defined %}
+{% for group_name, group_conf in group.network_group.items() %}
+define N_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.ipv6_network_group is defined %}
+{% for group_name, group_conf in group.ipv6_network_group.items() %}
+define N6_{{ group_name }} = { {{ group_conf.network | join(",") }} }
+{% endfor %}
+{% endif %}
+{% if group.port_group is defined %}
+{% for group_name, group_conf in group.port_group.items() %}
+define P_{{ group_name }} = { {{ group_conf.port | join(",") }} }
+{% endfor %}
+{% endif %}
+{% endif %} \ No newline at end of file
diff --git a/data/templates/firewall/nftables-policy.tmpl b/data/templates/firewall/nftables-policy.tmpl
index aa6bb6fc1..484b6f203 100644
--- a/data/templates/firewall/nftables-policy.tmpl
+++ b/data/templates/firewall/nftables-policy.tmpl
@@ -1,5 +1,13 @@
#!/usr/sbin/nft -f
+{% if cleanup_commands is defined %}
+{% for command in cleanup_commands %}
+{{ command }}
+{% endfor %}
+{% endif %}
+
+include "/run/nftables_defines.conf"
+
table ip mangle {
{% if first_install is defined %}
chain VYOS_PBR_PREROUTING {
@@ -9,7 +17,7 @@ table ip mangle {
type filter hook postrouting priority -150; policy accept;
}
{% endif %}
-{% if route is defined -%}
+{% if route is defined and route is not none -%}
{% for route_text, conf in route.items() %}
chain VYOS_PBR_{{ route_text }} {
{% if conf.rule is defined %}
@@ -36,8 +44,8 @@ table ip6 mangle {
type filter hook postrouting priority -150; policy accept;
}
{% endif %}
-{% if ipv6_route is defined %}
-{% for route_text, conf in ipv6_route.items() %}
+{% if route6 is defined and route6 is not none %}
+{% for route_text, conf in route6.items() %}
chain VYOS_PBR6_{{ route_text }} {
{% if conf.rule is defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl
index 68e83de64..81b2c0b98 100644
--- a/data/templates/firewall/nftables.tmpl
+++ b/data/templates/firewall/nftables.tmpl
@@ -6,43 +6,7 @@
{% 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 %}
+include "/run/nftables_defines.conf"
table ip filter {
{% if first_install is defined %}
@@ -211,6 +175,7 @@ table raw {
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
notrack
}
@@ -219,6 +184,7 @@ table raw {
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
notrack
}
@@ -256,6 +222,10 @@ table raw {
chain VYOS_CT_OUTPUT_HOOK {
return
}
+
+ chain FW_CONNTRACK {
+ accept
+ }
}
table ip6 raw {
@@ -266,12 +236,14 @@ table ip6 raw {
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_PREROUTING_HOOK
+ counter jump FW_CONNTRACK
notrack
}
chain OUTPUT {
type filter hook output priority -300; policy accept;
counter jump VYOS_CT_OUTPUT_HOOK
+ counter jump FW_CONNTRACK
notrack
}
@@ -282,5 +254,9 @@ table ip6 raw {
chain VYOS_CT_OUTPUT_HOOK {
return
}
+
+ chain FW_CONNTRACK {
+ accept
+ }
}
{% endif %}
diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl
index af66baf53..a6618b6af 100644
--- a/data/templates/frr/ospfd.frr.tmpl
+++ b/data/templates/frr/ospfd.frr.tmpl
@@ -126,6 +126,9 @@ router ospf {{ 'vrf ' + vrf if vrf is defined and vrf is not none }}
{% if default_metric is defined and default_metric is not none %}
default-metric {{ default_metric }}
{% endif %}
+{% if maximum_paths is defined and maximum_paths is not none %}
+ maximum-paths {{ maximum_paths }}
+{% endif %}
{% if distance is defined and distance is not none %}
{% if distance.global is defined and distance.global is not none %}
distance {{ distance.global }}
diff --git a/data/templates/high-availability/keepalived.conf.tmpl b/data/templates/high-availability/keepalived.conf.tmpl
index afd5f5383..68c707f17 100644
--- a/data/templates/high-availability/keepalived.conf.tmpl
+++ b/data/templates/high-availability/keepalived.conf.tmpl
@@ -28,6 +28,9 @@ vrrp_instance {{ name }} {
virtual_router_id {{ group_config.vrid }}
priority {{ group_config.priority }}
advert_int {{ group_config.advertise_interval }}
+{% if group_config.track is defined and group_config.track.exclude_vrrp_interface is defined %}
+ dont_track_primary
+{% endif %}
{% if group_config.no_preempt is not defined and group_config.preempt_delay is defined and group_config.preempt_delay is not none %}
preempt_delay {{ group_config.preempt_delay }}
{% elif group_config.no_preempt is defined %}
@@ -61,8 +64,8 @@ vrrp_instance {{ name }} {
{% endif %}
{% if group_config.address is defined and group_config.address is not none %}
virtual_ipaddress {
-{% for addr in group_config.address %}
- {{ addr }}
+{% for addr, addr_config in group_config.address.items() %}
+ {{ addr }}{{ ' dev ' + addr_config.interface if addr_config.interface is defined }}
{% endfor %}
}
{% endif %}
@@ -73,6 +76,13 @@ vrrp_instance {{ name }} {
{% endfor %}
}
{% endif %}
+{% if group_config.track is defined and group_config.track.interface is defined and group_config.track.interface is not none %}
+ track_interface {
+{% for interface in group_config.track.interface %}
+ {{ interface }}
+{% endfor %}
+ }
+{% endif %}
{% if group_config.health_check is defined and group_config.health_check.script is defined and group_config.health_check.script is not none %}
track_script {
healthcheck_{{ name }}
@@ -103,7 +113,7 @@ vrrp_sync_group {{ name }} {
{% endif %}
{% endfor %}
{% endif %}
-{% if vrrp.conntrack_sync_group is defined and vrrp.conntrack_sync_group == name %}
+{% if conntrack_sync_group is defined and conntrack_sync_group == name %}
{% set vyos_helper = "/usr/libexec/vyos/vyos-vrrp-conntracksync.sh" %}
notify_master "{{ vyos_helper }} master {{ name }}"
notify_backup "{{ vyos_helper }} backup {{ name }}"
@@ -113,8 +123,8 @@ vrrp_sync_group {{ name }} {
{% endfor %}
{% endif %}
-# Virtual-server configuration
{% if virtual_server is defined and virtual_server is not none %}
+# Virtual-server configuration
{% for vserver, vserver_config in virtual_server.items() %}
virtual_server {{ vserver }} {{ vserver_config.port }} {
delay_loop {{ vserver_config.delay_loop }}
diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl
index 62fa4df7a..afc04aa6d 100644
--- a/data/templates/monitoring/telegraf.tmpl
+++ b/data/templates/monitoring/telegraf.tmpl
@@ -41,6 +41,7 @@
files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"]
dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"]
[[inputs.ethtool]]
+ interface_include = {{ interfaces_ethernet }}
[[inputs.iptables]]
use_sudo = false
table = "filter"
diff --git a/data/templates/nhrp/opennhrp.conf.tmpl b/data/templates/nhrp/opennhrp.conf.tmpl
index 948327198..e9e9f692a 100644
--- a/data/templates/nhrp/opennhrp.conf.tmpl
+++ b/data/templates/nhrp/opennhrp.conf.tmpl
@@ -33,7 +33,7 @@ interface {{ name }} #{{ type }} {{ profile_name }}
{% endfor %}
{% if tunnel_conf.shortcut_target is defined and tunnel_conf.shortcut_target is not none %}
{% for target, shortcut_conf in tunnel_conf.shortcut_target.items() %}
- shortcut-target {{ target }} {{ shortcut_conf.holding_time if shortcut_conf.holding_time is defined else '' }}
+ shortcut-target {{ target }}{{ ' holding-time ' + shortcut_conf.holding_time if shortcut_conf.holding_time is defined }}
{% endfor %}
{% endif %}
diff --git a/data/templates/ntp/ntpd.conf.tmpl b/data/templates/ntp/ntpd.conf.tmpl
index 38e68f24f..e7afcc16b 100644
--- a/data/templates/ntp/ntpd.conf.tmpl
+++ b/data/templates/ntp/ntpd.conf.tmpl
@@ -27,6 +27,7 @@ restrict -6 ::1
{% if allow_clients is defined and allow_clients.address is defined %}
# Allowed clients configuration
+restrict default ignore
{% for address in allow_clients.address %}
restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer
{% endfor %}
diff --git a/data/templates/zone_policy/nftables.tmpl b/data/templates/zone_policy/nftables.tmpl
index fae6e8c4f..e59208a0d 100644
--- a/data/templates/zone_policy/nftables.tmpl
+++ b/data/templates/zone_policy/nftables.tmpl
@@ -29,6 +29,9 @@ table ip filter {
{% else %}
chain VZONE_{{ zone_name }} {
iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=False) }}
+{% if zone_conf.intra_zone_filtering is defined %}
+ iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% endif %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is defined %}
{% if zone[from_zone].local_zone is not defined %}
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.name }}
@@ -63,6 +66,9 @@ table ip6 filter {
{% else %}
chain VZONE6_{{ zone_name }} {
iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }}
+{% if zone_conf.intra_zone_filtering is defined %}
+ iifname { {{ zone_conf.interface | join(",") }} } counter return
+{% endif %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is defined %}
{% if zone[from_zone].local_zone is not defined %}
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump {{ from_conf.firewall.ipv6_name }}
diff --git a/debian/control b/debian/control
index 3d33a48a6..8afdd3300 100644
--- a/debian/control
+++ b/debian/control
@@ -94,6 +94,7 @@ Depends:
ndisc6,
ndppd,
netplug,
+ nfct,
nftables (>= 0.9.3),
nginx-light,
ntp,
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 78a48a522..987ccaca6 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -109,8 +109,13 @@
<format>ipv6</format>
<description>IPv6 address to match</description>
</valueHelp>
+ <valueHelp>
+ <format>ipv6range</format>
+ <description>IPv6 range to match (e.g. 2002::1-2002::ff)</description>
+ </valueHelp>
<constraint>
<validator name="ipv6-address"/>
+ <validator name="ipv6-range"/>
</constraint>
<multi/>
</properties>
@@ -120,7 +125,7 @@
</tagNode>
<tagNode name="ipv6-network-group">
<properties>
- <help>Network-group member</help>
+ <help>Firewall ipv6-network-group</help>
</properties>
<children>
#include <include/generic-description.xml.i>
@@ -139,6 +144,27 @@
</leafNode>
</children>
</tagNode>
+ <tagNode name="mac-group">
+ <properties>
+ <help>Firewall mac-group</help>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <leafNode name="mac-address">
+ <properties>
+ <help>Mac-group member</help>
+ <valueHelp>
+ <format>&lt;MAC address&gt;</format>
+ <description>MAC address to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
<tagNode name="network-group">
<properties>
<help>Firewall network-group</help>
@@ -182,6 +208,9 @@
<description>Numbered port range (e.g. 1001-1050)</description>
</valueHelp>
<multi/>
+ <constraint>
+ <validator name="port-range"/>
+ </constraint>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index f46343c76..ee1d70484 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -177,8 +177,37 @@
<valueless/>
</properties>
</leafNode>
+ <node name="track">
+ <properties>
+ <help>Track settings</help>
+ </properties>
+ <children>
+ <leafNode name="exclude-vrrp-interface">
+ <properties>
+ <valueless/>
+ <help>Disable track state of main interface</help>
+ </properties>
+ </leafNode>
+ <leafNode name="interface">
+ <properties>
+ <help>Interface name state check</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="interface-name"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
#include <include/vrrp-transition-script.xml.i>
- <leafNode name="address">
+ <tagNode name="address">
<properties>
<help>Virtual IP address</help>
<valueHelp>
@@ -193,9 +222,11 @@
<validator name="ipv4-host"/>
<validator name="ipv6-host"/>
</constraint>
- <multi/>
</properties>
- </leafNode>
+ <children>
+ #include <include/generic-interface-broadcast.xml.i>
+ </children>
+ </tagNode>
<leafNode name="excluded-address">
<properties>
<help>Virtual address (If you need additional IPv4 and IPv6 in same group)</help>
diff --git a/interface-definitions/include/conntrack/log-common.xml.i b/interface-definitions/include/conntrack/log-common.xml.i
new file mode 100644
index 000000000..38799f8f4
--- /dev/null
+++ b/interface-definitions/include/conntrack/log-common.xml.i
@@ -0,0 +1,20 @@
+<!-- include start from conntrack/log-common.xml.i -->
+<leafNode name="destroy">
+ <properties>
+ <help>Log connection deletion</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="new">
+ <properties>
+ <help>Log connection creation</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="update">
+ <properties>
+ <help>Log connection updates</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/conntrack/timeout-common-protocols.xml.i b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i
new file mode 100644
index 000000000..2676d846e
--- /dev/null
+++ b/interface-definitions/include/conntrack/timeout-common-protocols.xml.i
@@ -0,0 +1,172 @@
+<!-- include start from conntrack/timeout-common-protocols.xml.i -->
+<leafNode name="icmp">
+ <properties>
+ <help>ICMP timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>ICMP timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+</leafNode>
+<leafNode name="other">
+ <properties>
+ <help>Generic connection timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>Generic connection timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>600</defaultValue>
+</leafNode>
+<node name="tcp">
+ <properties>
+ <help>TCP connection timeout options</help>
+ </properties>
+ <children>
+ <leafNode name="close-wait">
+ <properties>
+ <help>TCP CLOSE-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP CLOSE-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="close">
+ <properties>
+ <help>TCP CLOSE timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP CLOSE timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="established">
+ <properties>
+ <help>TCP ESTABLISHED timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP ESTABLISHED timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>432000</defaultValue>
+ </leafNode>
+ <leafNode name="fin-wait">
+ <properties>
+ <help>TCP FIN-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP FIN-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="last-ack">
+ <properties>
+ <help>TCP LAST-ACK timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP LAST-ACK timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="syn-recv">
+ <properties>
+ <help>TCP SYN-RECEIVED timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP SYN-RECEIVED timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="syn-sent">
+ <properties>
+ <help>TCP SYN-SENT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP SYN-SENT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ <leafNode name="time-wait">
+ <properties>
+ <help>TCP TIME-WAIT timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>TCP TIME-WAIT timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>120</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<node name="udp">
+ <properties>
+ <help>UDP timeout options</help>
+ </properties>
+ <children>
+ <leafNode name="other">
+ <properties>
+ <help>UDP generic timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>UDP generic timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>30</defaultValue>
+ </leafNode>
+ <leafNode name="stream">
+ <properties>
+ <help>UDP stream timeout in seconds</help>
+ <valueHelp>
+ <format>u32:1-21474836</format>
+ <description>UDP stream timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-21474836"/>
+ </constraint>
+ </properties>
+ <defaultValue>180</defaultValue>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 727200ed7..521fe54f2 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -100,6 +100,7 @@
<help>Protocol to match (protocol name, number, or "all")</help>
<completionHelp>
<script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
</completionHelp>
<valueHelp>
<format>all</format>
@@ -114,8 +115,12 @@
<description>IP protocol number</description>
</valueHelp>
<valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
<format>!&lt;protocol&gt;</format>
- <description>IP protocol number</description>
+ <description>IP protocol name</description>
</valueHelp>
<constraint>
<validator name="ip-protocol"/>
@@ -171,6 +176,9 @@
<format>!&lt;MAC address&gt;</format>
<description>Match everything except the specified MAC address</description>
</valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
</properties>
</leafNode>
#include <include/firewall/port.xml.i>
@@ -259,26 +267,7 @@
</leafNode>
</children>
</node>
-<node name="tcp">
- <properties>
- <help>TCP flags to match</help>
- </properties>
- <children>
- <leafNode name="flags">
- <properties>
- <help>TCP flags to match</help>
- <valueHelp>
- <format>txt</format>
- <description>TCP flags to match</description>
- </valueHelp>
- <valueHelp>
- <format> </format>
- <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description>
- </valueHelp>
- </properties>
- </leafNode>
- </children>
-</node>
+#include <include/firewall/tcp-flags.xml.i>
<node name="time">
<properties>
<help>Time to match rule</help>
diff --git a/interface-definitions/include/firewall/mac-group.xml.i b/interface-definitions/include/firewall/mac-group.xml.i
new file mode 100644
index 000000000..dbce3fc88
--- /dev/null
+++ b/interface-definitions/include/firewall/mac-group.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from firewall/mac-group.xml.i -->
+<leafNode name="mac-group">
+ <properties>
+ <help>Group of MAC addresses</help>
+ <completionHelp>
+ <path>firewall group mac-group</path>
+ </completionHelp>
+ </properties>
+</leafNode>
+<!-- include start from firewall/mac-group.xml.i --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/port.xml.i b/interface-definitions/include/firewall/port.xml.i
index 59d92978b..3bacafff8 100644
--- a/interface-definitions/include/firewall/port.xml.i
+++ b/interface-definitions/include/firewall/port.xml.i
@@ -16,8 +16,11 @@
</valueHelp>
<valueHelp>
<format> </format>
- <description>\n\n Multiple destination ports can be specified as a comma-separated list.\n The whole list can also be negated using '!'.\n For example: '!22,telnet,http,123,1001-1005'</description>
+ <description>\n\n Multiple destination ports can be specified as a comma-separated list.\n For example: 'telnet,http,123,1001-1005'</description>
</valueHelp>
+ <constraint>
+ <validator name="port-multi"/>
+ </constraint>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
index 7815b78d4..c2cc7edb3 100644
--- a/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group-ipv6.xml.i
@@ -12,6 +12,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/firewall/mac-group.xml.i>
<leafNode name="network-group">
<properties>
<help>Group of networks</help>
diff --git a/interface-definitions/include/firewall/source-destination-group.xml.i b/interface-definitions/include/firewall/source-destination-group.xml.i
index 9a9bed0fe..ab11e89e9 100644
--- a/interface-definitions/include/firewall/source-destination-group.xml.i
+++ b/interface-definitions/include/firewall/source-destination-group.xml.i
@@ -12,6 +12,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/firewall/mac-group.xml.i>
<leafNode name="network-group">
<properties>
<help>Group of networks</help>
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
new file mode 100644
index 000000000..b99896687
--- /dev/null
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -0,0 +1,119 @@
+<!-- include start from firewall/tcp-flags.xml.i -->
+<node name="tcp">
+ <properties>
+ <help>TCP flags to match</help>
+ </properties>
+ <children>
+ <node name="flags">
+ <properties>
+ <help>TCP flags to match</help>
+ </properties>
+ <children>
+ <leafNode name="syn">
+ <properties>
+ <help>Synchronise flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ack">
+ <properties>
+ <help>Acknowledge flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="fin">
+ <properties>
+ <help>Finish flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rst">
+ <properties>
+ <help>Reset flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="urg">
+ <properties>
+ <help>Urgent flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="psh">
+ <properties>
+ <help>Push flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ecn">
+ <properties>
+ <help>Explicit Congestion Notification flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cwr">
+ <properties>
+ <help>Congestion Window Reduced flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="not">
+ <properties>
+ <help>Match flags not set</help>
+ </properties>
+ <children>
+ <leafNode name="syn">
+ <properties>
+ <help>Synchronise flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ack">
+ <properties>
+ <help>Acknowledge flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="fin">
+ <properties>
+ <help>Finish flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rst">
+ <properties>
+ <help>Reset flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="urg">
+ <properties>
+ <help>Urgent flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="psh">
+ <properties>
+ <help>Push flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ecn">
+ <properties>
+ <help>Explicit Congestion Notification flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cwr">
+ <properties>
+ <help>Congestion Window Reduced flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/interface-policy-vif-c.xml.i b/interface-definitions/include/interface/interface-policy-vif-c.xml.i
index 5dad6422b..866fcd5c0 100644
--- a/interface-definitions/include/interface/interface-policy-vif-c.xml.i
+++ b/interface-definitions/include/interface/interface-policy-vif-c.xml.i
@@ -13,11 +13,11 @@
</completionHelp>
</properties>
</leafNode>
- <leafNode name="ipv6-route">
+ <leafNode name="route6">
<properties>
<help>IPv6 policy route ruleset for interface</help>
<completionHelp>
- <path>policy ipv6-route</path>
+ <path>policy route6</path>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/interface-policy-vif.xml.i b/interface-definitions/include/interface/interface-policy-vif.xml.i
index 5ee80ae13..83510fe59 100644
--- a/interface-definitions/include/interface/interface-policy-vif.xml.i
+++ b/interface-definitions/include/interface/interface-policy-vif.xml.i
@@ -13,11 +13,11 @@
</completionHelp>
</properties>
</leafNode>
- <leafNode name="ipv6-route">
+ <leafNode name="route6">
<properties>
<help>IPv6 policy route ruleset for interface</help>
<completionHelp>
- <path>policy ipv6-route</path>
+ <path>policy route6</path>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface/interface-policy.xml.i b/interface-definitions/include/interface/interface-policy.xml.i
index 06f025af1..42a8fd009 100644
--- a/interface-definitions/include/interface/interface-policy.xml.i
+++ b/interface-definitions/include/interface/interface-policy.xml.i
@@ -13,11 +13,11 @@
</completionHelp>
</properties>
</leafNode>
- <leafNode name="ipv6-route">
+ <leafNode name="route6">
<properties>
<help>IPv6 policy route ruleset for interface</help>
<completionHelp>
- <path>policy ipv6-route</path>
+ <path>policy route6</path>
</completionHelp>
</properties>
</leafNode>
diff --git a/interface-definitions/include/nat-port.xml.i b/interface-definitions/include/nat-port.xml.i
index ebba43712..7aabc33c3 100644
--- a/interface-definitions/include/nat-port.xml.i
+++ b/interface-definitions/include/nat-port.xml.i
@@ -11,7 +11,7 @@
<description>Numbered port range (e.g. 1001-1005)</description>
</valueHelp>
<valueHelp>
- <format> </format>
+ <format/>
<description>\n\nMultiple destination ports can be specified as a comma-separated list.\nThe whole list can also be negated using '!'.\nFor example: '!22,telnet,http,123,1001-1005'</description>
</valueHelp>
</properties>
diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i
index 084f1f722..bdb86ed9b 100644
--- a/interface-definitions/include/nat-rule.xml.i
+++ b/interface-definitions/include/nat-rule.xml.i
@@ -4,7 +4,7 @@
<help>Rule number for NAT</help>
<valueHelp>
<format>u32:1-999999</format>
- <description>Number for this NAT rule</description>
+ <description>Number of NAT rule</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
@@ -12,11 +12,7 @@
<constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
- <leafNode name="description">
- <properties>
- <help>Rule description</help>
- </properties>
- </leafNode>
+ #include <include/generic-description.xml.i>
<node name="destination">
<properties>
<help>NAT destination parameters</help>
diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i
index 688e78034..e783f4bec 100644
--- a/interface-definitions/include/ospf/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospf/protocol-common-config.xml.i
@@ -289,6 +289,18 @@
</constraint>
</properties>
</leafNode>
+<leafNode name="maximum-paths">
+ <properties>
+ <help>Maximum multiple paths (ECMP)</help>
+ <valueHelp>
+ <format>u32:1-64</format>
+ <description>Maximum multiple paths (ECMP)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-64"/>
+ </constraint>
+ </properties>
+</leafNode>
<node name="distance">
<properties>
<help>Administrative distance</help>
diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
index 2d6adcd1d..406125e55 100644
--- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
+++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i
@@ -232,6 +232,9 @@
<format>!&lt;MAC address&gt;</format>
<description>Match everything except the specified MAC address</description>
</valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
</properties>
</leafNode>
#include <include/firewall/port.xml.i>
@@ -320,26 +323,7 @@
</leafNode>
</children>
</node>
-<node name="tcp">
- <properties>
- <help>TCP flags to match</help>
- </properties>
- <children>
- <leafNode name="flags">
- <properties>
- <help>TCP flags to match</help>
- <valueHelp>
- <format>txt</format>
- <description>TCP flags to match</description>
- </valueHelp>
- <valueHelp>
- <format> </format>
- <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description>
- </valueHelp>
- </properties>
- </leafNode>
- </children>
-</node>
+#include <include/firewall/tcp-flags.xml.i>
<node name="time">
<properties>
<help>Time to match rule</help>
diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i
index c4deefd2a..33c4ba77c 100644
--- a/interface-definitions/include/policy/route-common-rule.xml.i
+++ b/interface-definitions/include/policy/route-common-rule.xml.i
@@ -232,6 +232,9 @@
<format>!&lt;MAC address&gt;</format>
<description>Match everything except the specified MAC address</description>
</valueHelp>
+ <constraint>
+ <validator name="mac-address-firewall"/>
+ </constraint>
</properties>
</leafNode>
#include <include/firewall/port.xml.i>
@@ -320,26 +323,7 @@
</leafNode>
</children>
</node>
-<node name="tcp">
- <properties>
- <help>TCP flags to match</help>
- </properties>
- <children>
- <leafNode name="flags">
- <properties>
- <help>TCP flags to match</help>
- <valueHelp>
- <format>txt</format>
- <description>TCP flags to match</description>
- </valueHelp>
- <valueHelp>
- <format> </format>
- <description>\n\n Allowed values for TCP flags : SYN ACK FIN RST URG PSH ALL\n When specifying more than one flag, flags should be comma-separated.\n For example : value of 'SYN,!ACK,!FIN,!RST' will only match packets with\n the SYN flag set, and the ACK, FIN and RST flags unset</description>
- </valueHelp>
- </properties>
- </leafNode>
- </children>
-</node>
+#include <include/firewall/tcp-flags.xml.i>
<node name="time">
<properties>
<help>Time to match rule</help>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index 7a7c9c1d9..1b4b4a816 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -101,6 +101,7 @@
</valueHelp>
<constraint>
<validator name="ip-address"/>
+ <validator name="ipv6-link-local"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in
index ed726d1e4..4ce953b52 100644
--- a/interface-definitions/policy-route.xml.in
+++ b/interface-definitions/policy-route.xml.in
@@ -2,9 +2,9 @@
<interfaceDefinition>
<node name="policy">
<children>
- <tagNode name="ipv6-route" owner="${vyos_conf_scripts_dir}/policy-route.py">
+ <tagNode name="route6" owner="${vyos_conf_scripts_dir}/policy-route.py">
<properties>
- <help>IPv6 policy route rule set name</help>
+ <help>Policy route rule set name for IPv6</help>
<priority>201</priority>
</properties>
<children>
@@ -12,7 +12,15 @@
#include <include/firewall/name-default-log.xml.i>
<tagNode name="rule">
<properties>
- <help>Rule number (1-9999)</help>
+ <help>Policy rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of policy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
<node name="destination">
@@ -42,7 +50,7 @@
</tagNode>
<tagNode name="route" owner="${vyos_conf_scripts_dir}/policy-route.py">
<properties>
- <help>Policy route rule set name</help>
+ <help>Policy route rule set name for IPv4</help>
<priority>201</priority>
</properties>
<children>
@@ -50,7 +58,15 @@
#include <include/firewall/name-default-log.xml.i>
<tagNode name="rule">
<properties>
- <help>Rule number (1-9999)</help>
+ <help>Policy rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of policy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Policy rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
<node name="destination">
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 225f9a6f9..61c5ab90a 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -793,7 +793,7 @@
</node>
<leafNode name="local-preference">
<properties>
- <help>local-preference_help</help>
+ <help>Local Preference</help>
<valueHelp>
<format>u32:0-4294967295</format>
<description>Local Preference</description>
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index daa4177c9..65edab839 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -35,6 +35,128 @@
</properties>
<defaultValue>32768</defaultValue>
</leafNode>
+ <node name="ignore">
+ <properties>
+ <help>Customized rules to ignore selective connection tracking</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack ignore rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="log">
+ <properties>
+ <help>Log connection tracking events per protocol</help>
+ </properties>
+ <children>
+ <node name="icmp">
+ <properties>
+ <help>Log connection tracking events for ICMP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="other">
+ <properties>
+ <help>Log connection tracking events for all protocols other than TCP, UDP and ICMP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="tcp">
+ <properties>
+ <help>Log connection tracking events for TCP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ <node name="udp">
+ <properties>
+ <help>Log connection tracking events for UDP</help>
+ </properties>
+ <children>
+ #include <include/conntrack/log-common.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
<node name="modules">
<properties>
<help>Connection tracking modules</help>
@@ -155,176 +277,66 @@
<help>Connection timeout options</help>
</properties>
<children>
- <leafNode name="icmp">
- <properties>
- <help>ICMP timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>ICMP timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="other">
- <properties>
- <help>Generic connection timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>Generic connection timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>600</defaultValue>
- </leafNode>
- <node name="tcp">
- <properties>
- <help>TCP connection timeout options</help>
- </properties>
- <children>
- <leafNode name="close-wait">
- <properties>
- <help>TCP CLOSE-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP CLOSE-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>60</defaultValue>
- </leafNode>
- <leafNode name="close">
- <properties>
- <help>TCP CLOSE timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP CLOSE timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>10</defaultValue>
- </leafNode>
- <leafNode name="established">
- <properties>
- <help>TCP ESTABLISHED timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP ESTABLISHED timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>432000</defaultValue>
- </leafNode>
- <leafNode name="fin-wait">
- <properties>
- <help>TCP FIN-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP FIN-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- <leafNode name="last-ack">
- <properties>
- <help>TCP LAST-ACK timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP LAST-ACK timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="syn-recv">
- <properties>
- <help>TCP SYN-RECEIVED timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP SYN-RECEIVED timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>60</defaultValue>
- </leafNode>
- <leafNode name="syn-sent">
- <properties>
- <help>TCP SYN-SENT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP SYN-SENT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- <leafNode name="time-wait">
- <properties>
- <help>TCP TIME-WAIT timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>TCP TIME-WAIT timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>120</defaultValue>
- </leafNode>
- </children>
- </node>
- <node name="udp">
+ <node name="custom">
<properties>
- <help>UDP timeout options</help>
+ <help>Define custom timeouts per connection</help>
</properties>
<children>
- <leafNode name="other">
+ <tagNode name="rule">
<properties>
- <help>UDP generic timeout in seconds</help>
+ <help>Rule number</help>
<valueHelp>
- <format>u32:1-21474836</format>
- <description>UDP generic timeout in seconds</description>
+ <format>u32:1-999999</format>
+ <description>Number of conntrack rule</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
+ <validator name="numeric" argument="--range 1-999999"/>
</constraint>
+ <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
- <defaultValue>30</defaultValue>
- </leafNode>
- <leafNode name="stream">
- <properties>
- <help>UDP stream timeout in seconds</help>
- <valueHelp>
- <format>u32:1-21474836</format>
- <description>UDP stream timeout in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-21474836"/>
- </constraint>
- </properties>
- <defaultValue>180</defaultValue>
- </leafNode>
+ <children>
+ #include <include/generic-description.xml.i>
+ <node name="destination">
+ <properties>
+ <help>Destination parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Interface to ignore connections tracking on</help>
+ <completionHelp>
+ <list>any</list>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/ip-protocol.xml.i>
+ <node name="protocol">
+ <properties>
+ <help>Customize protocol specific timers, one protocol configuration per rule</help>
+ </properties>
+ <children>
+ #include <include/conntrack/timeout-common-protocols.xml.i>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Source parameters</help>
+ </properties>
+ <children>
+ #include <include/nat-address.xml.i>
+ #include <include/nat-port.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
+ #include <include/conntrack/timeout-common-protocols.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index 0c2205410..afa3d52a0 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -286,7 +286,7 @@
</node>
<leafNode name="ikev2-reauth">
<properties>
- <help>ikev2-reauth_help</help>
+ <help>Re-authentication of the remote peer during an IKE re-key. IKEv2 option only</help>
<completionHelp>
<list>yes no</list>
</completionHelp>
diff --git a/op-mode-definitions/policy-route.xml.in b/op-mode-definitions/policy-route.xml.in
index c998e5487..bd4a61dc9 100644
--- a/op-mode-definitions/policy-route.xml.in
+++ b/op-mode-definitions/policy-route.xml.in
@@ -84,17 +84,17 @@
<help>Show policy information</help>
</properties>
<children>
- <node name="ipv6-route">
+ <node name="route6">
<properties>
<help>Show IPv6 policy chain</help>
</properties>
<command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show_all --ipv6</command>
</node>
- <tagNode name="ipv6-route">
+ <tagNode name="route6">
<properties>
<help>Show IPv6 policy chains</help>
<completionHelp>
- <path>policy ipv6-route</path>
+ <path>policy route6</path>
</completionHelp>
</properties>
<children>
@@ -102,7 +102,7 @@
<properties>
<help>Show summary of IPv6 policy rules</help>
<completionHelp>
- <path>policy ipv6-route ${COMP_WORDS[4]} rule</path>
+ <path>policy route6 ${COMP_WORDS[4]} rule</path>
</completionHelp>
</properties>
<command>sudo ${vyos_op_scripts_dir}/policy_route.py --action show --name $4 --rule $6 --ipv6</command>
diff --git a/op-mode-definitions/show-virtual-server.xml.in b/op-mode-definitions/show-virtual-server.xml.in
new file mode 100644
index 000000000..5dbd3c759
--- /dev/null
+++ b/op-mode-definitions/show-virtual-server.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="virtual-server">
+ <properties>
+ <help>Show virtual server information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_virtual_server.py</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 8b7402b7e..2ab78ff18 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -45,13 +45,19 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
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 states:
+ output.append(f'ct state {{{states}}}')
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
proto = rule_conf['protocol']
+ operator = ''
+ if proto[0] == '!':
+ operator = '!='
+ proto = proto[1:]
if proto == 'tcp_udp':
proto = '{tcp, udp}'
- output.append('meta l4proto ' + proto)
+ output.append(f'meta l4proto {operator} {proto}')
for side in ['destination', 'source']:
if side in rule_conf:
@@ -59,7 +65,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
side_conf = rule_conf[side]
if 'address' in side_conf:
- output.append(f'{ip_name} {prefix}addr {side_conf["address"]}')
+ suffix = side_conf['address']
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'{ip_name} {prefix}addr {suffix}')
if 'mac_address' in side_conf:
suffix = side_conf["mac_address"]
@@ -69,15 +78,27 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'port' in side_conf:
proto = rule_conf['protocol']
- port = side_conf["port"]
+ port = side_conf['port'].split(',')
- if isinstance(port, list):
- port = ",".join(port)
+ ports = []
+ negated_ports = []
+
+ for p in port:
+ if p[0] == '!':
+ negated_ports.append(p[1:])
+ else:
+ ports.append(p)
if proto == 'tcp_udp':
proto = 'th'
- output.append(f'{proto} {prefix}port {{{port}}}')
+ if ports:
+ ports_str = ','.join(ports)
+ output.append(f'{proto} {prefix}port {{{ports_str}}}')
+
+ if negated_ports:
+ negated_ports_str = ','.join(negated_ports)
+ output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}')
if 'group' in side_conf:
group = side_conf['group']
@@ -87,6 +108,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
elif 'network_group' in group:
group_name = group['network_group']
output.append(f'{ip_name} {prefix}addr $N{def_suffix}_{group_name}')
+ if 'mac_group' in group:
+ group_name = group['mac_group']
+ output.append(f'ether {prefix}addr $M_{group_name}')
if 'port_group' in group:
proto = rule_conf['protocol']
group_name = group['port_group']
@@ -150,7 +174,6 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if tcp_flags:
output.append(parse_tcp_flags(tcp_flags))
-
output.append('counter')
if 'set' in rule_conf:
@@ -165,14 +188,8 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
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)
+ include = [flag for flag in flags if flag != 'not']
+ all_flags = include + [flag for flag in flags['not']] if 'not' in flags else []
return f'tcp flags & ({"|".join(all_flags)}) == {"|".join(include)}'
def parse_time(time):
@@ -209,7 +226,7 @@ def parse_policy_set(set_conf, def_suffix):
table = set_conf['table']
if table == 'main':
table = '254'
- mark = 0x7FFFFFFF - int(set_conf['table'])
+ mark = 0x7FFFFFFF - int(table)
out.append(f'meta mark set {mark}')
if 'tcp_mss' in set_conf:
mss = set_conf['tcp_mss']
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index a8f115d9a..cbba19ab7 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -73,15 +73,15 @@ from vyos.util import cmd
import logging
from logging.handlers import SysLogHandler
import os
+import sys
+
LOG = logging.getLogger(__name__)
+DEBUG = False
-DEBUG = os.path.exists('/tmp/vyos.frr.debug')
-if DEBUG:
- LOG.setLevel(logging.DEBUG)
- ch = SysLogHandler(address='/dev/log')
- ch2 = logging.StreamHandler()
- LOG.addHandler(ch)
- LOG.addHandler(ch2)
+ch = SysLogHandler(address='/dev/log')
+ch2 = logging.StreamHandler(stream=sys.stdout)
+LOG.addHandler(ch)
+LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
@@ -121,6 +121,12 @@ class ConfigSectionNotFound(FrrError):
"""
pass
+def init_debugging():
+ global DEBUG
+
+ DEBUG = os.path.exists('/tmp/vyos.frr.debug')
+ if DEBUG:
+ LOG.setLevel(logging.DEBUG)
def get_configuration(daemon=None, marked=False):
""" Get current running FRR configuration
@@ -424,6 +430,8 @@ class FRRConfig:
Using this overwrites the current loaded config objects and replaces the original loaded config
'''
+ init_debugging()
+
self.imported_config = get_configuration(daemon=daemon)
if daemon:
LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}')
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index aa62ac60d..66044fa52 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -83,8 +83,7 @@ def check_storage(path, size):
directory = path if os.path.isdir(path) else (os.path.dirname(os.path.expanduser(path)) or os.getcwd())
# `size` can be None or 0 to indicate unknown size.
if not size:
- print_error('Warning: Cannot determine size of remote file.')
- print_error('Bravely continuing regardless.')
+ print_error('Warning: Cannot determine size of remote file. Bravely continuing regardless.')
return
if size < 1024 * 1024:
@@ -227,7 +226,7 @@ class HttpC:
r.raise_for_status()
# If the request got redirected, keep the last URL we ended up with.
final_urlstring = r.url
- if r.history:
+ if r.history and self.progressbar:
print_error('Redirecting to ' + final_urlstring)
# Check for the prospective file size.
try:
@@ -317,11 +316,12 @@ def friendly_download(local_path, urlstring, source_host='', source_port=0):
sys.exit(1)
except:
import traceback
+ print_error(f'Failed to download {urlstring}.')
# There are a myriad different reasons a download could fail.
# SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...)
# We omit the scary stack trace but print the error nevertheless.
- print_error(f'Failed to download {urlstring}.')
- traceback.print_exception(*sys.exc_info()[:2], None)
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ traceback.print_exception(exc_type, exc_value, None, 0, None, False)
sys.exit(1)
else:
print_error('Download complete.')
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index af7c075e4..63d955738 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -6,6 +6,15 @@ firewall {
ipv6-src-route disable
ip-src-route disable
log-martians enable
+ name test_tcp_flags {
+ rule 1 {
+ action drop
+ protocol tcp
+ tcp {
+ flags SYN,ACK,!RST,!FIN
+ }
+ }
+ }
options {
interface vtun0 {
adjust-mss 1380
@@ -83,6 +92,7 @@ interfaces {
}
policy {
route LAN-POLICY-BASED-ROUTING
+ ipv6-route LAN6-POLICY-BASED-ROUTING
}
smp-affinity auto
speed auto
@@ -383,6 +393,29 @@ nat {
}
}
policy {
+ ipv6-route LAN6-POLICY-BASED-ROUTING {
+ rule 10 {
+ destination {
+ }
+ disable
+ set {
+ table 10
+ }
+ source {
+ address 2002::1
+ }
+ }
+ rule 20 {
+ destination {
+ }
+ set {
+ table 100
+ }
+ source {
+ address 2008::f
+ }
+ }
+ }
prefix-list user2-routes {
rule 1 {
action permit
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 2b3b354ba..6b74e6c92 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -46,6 +46,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_groups(self):
+ self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05'])
self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
@@ -53,7 +54,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
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(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -61,7 +64,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['iifname "eth0"', 'jump smoketest'],
- ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'tcp dport { 53, 123 }', 'return'],
+ ['ip saddr { 172.16.99.0/24 }', 'ip daddr 172.16.10.10', 'th dport { 53, 123 }', 'return'],
+ ['ether saddr { 00:01:02:03:04:05 }', 'return']
]
nftables_output = cmd('sudo nft list table ip filter')
@@ -72,7 +76,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_basic_rules(self):
self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop'])
@@ -80,8 +84,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
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', 'protocol', 'tcp'])
self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'destination', 'port', '8888'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn'])
+ self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest'])
@@ -90,7 +96,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
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'],
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'reject'],
['smoketest default-action', 'drop']
]
@@ -102,7 +108,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_basic_rules_ipv6(self):
self.cli_set(['firewall', 'ipv6-name', 'v6-smoketest', 'default-action', 'drop'])
@@ -132,7 +138,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
if all(item in line for item in search):
matched = True
break
- self.assertTrue(matched)
+ self.assertTrue(matched, msg=search)
def test_state_policy(self):
self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept'])
diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py
index a37ce7e64..68905e447 100755
--- a/smoketest/scripts/cli/test_ha_vrrp.py
+++ b/smoketest/scripts/cli/test_ha_vrrp.py
@@ -166,5 +166,35 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
for group in groups:
self.assertIn(f'{group}', config)
+ def test_04_exclude_vrrp_interface(self):
+ group = 'VyOS-WAN'
+ none_vrrp_interface = 'eth2'
+ vlan_id = '24'
+ vip = '100.64.24.1/24'
+ vip_dev = '192.0.2.2/24'
+ vrid = '150'
+ group_base = base_path + ['vrrp', 'group', group]
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24'])
+ self.cli_set(group_base + ['interface', f'{vrrp_interface}.{vlan_id}'])
+ self.cli_set(group_base + ['address', vip])
+ self.cli_set(group_base + ['address', vip_dev, 'interface', none_vrrp_interface])
+ self.cli_set(group_base + ['track', 'exclude-vrrp-interface'])
+ self.cli_set(group_base + ['track', 'interface', none_vrrp_interface])
+ self.cli_set(group_base + ['vrid', vrid])
+
+ # commit changes
+ self.cli_commit()
+
+ config = getConfig(f'vrrp_instance {group}')
+
+ self.assertIn(f'interface {vrrp_interface}.{vlan_id}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'dont_track_primary', config)
+ self.assertIn(f' {vip}', config)
+ self.assertIn(f' {vip_dev} dev {none_vrrp_interface}', config)
+ self.assertIn(f'track_interface', config)
+ self.assertIn(f' {none_vrrp_interface}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index 70a234187..9035f0832 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -31,8 +31,9 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
self.cli_delete(['interfaces', 'ethernet', 'eth0'])
+ self.cli_delete(['protocols', 'static'])
self.cli_delete(['policy', 'route'])
- self.cli_delete(['policy', 'ipv6-route'])
+ self.cli_delete(['policy', 'route6'])
self.cli_commit()
def test_pbr_mark(self):
@@ -62,19 +63,27 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
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', 'protocol', 'tcp'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'syn'])
+ self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'tcp', 'flags', 'not', 'ack'])
self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'table', table_id])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'tcp_udp'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888'])
+ self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route', 'smoketest'])
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'policy', 'route6', 'smoketest6'])
self.cli_commit()
mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id))
+ # IPv4
+
nftables_search = [
['iifname "eth0"', 'jump VYOS_PBR_smoketest'],
- ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
+ ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex]
]
nftables_output = cmd('sudo nft list table ip mangle')
@@ -87,6 +96,25 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
break
self.assertTrue(matched)
+ # IPv6
+
+ nftables6_search = [
+ ['iifname "eth0"', 'jump VYOS_PBR6_smoketest'],
+ ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex]
+ ]
+
+ nftables6_output = cmd('sudo nft list table ip6 mangle')
+
+ for search in nftables6_search:
+ matched = False
+ for line in nftables6_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(matched)
+
+ # IP rule fwmark -> table
+
ip_rule_search = [
['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id]
]
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index e8cc64463..c8cf04b7d 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import re
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -29,17 +28,14 @@ PROCESS_NAME = 'ntpd'
NTP_CONF = '/run/ntpd/ntpd.conf'
base_path = ['system', 'ntp']
-def get_config_value(key):
- tmp = read_file(NTP_CONF)
- tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp)
- # remove possible trailing whitespaces
- return [item.strip() for item in tmp]
-
class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
- self.cli_delete(base_path)
+ cls.cli_delete(cls, base_path)
def tearDown(self):
self.cli_delete(base_path)
@@ -47,35 +43,38 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
self.assertFalse(process_named_running(PROCESS_NAME))
- def test_ntp_options(self):
+ def test_01_ntp_options(self):
# Test basic NTP support with multiple servers and their options
servers = ['192.0.2.1', '192.0.2.2']
options = ['noselect', 'preempt', 'prefer']
- ntp_pool = 'pool.vyos.io'
+ pools = ['pool.vyos.io']
for server in servers:
for option in options:
self.cli_set(base_path + ['server', server, option])
# Test NTP pool
- self.cli_set(base_path + ['server', ntp_pool, 'pool'])
+ for pool in pools:
+ self.cli_set(base_path + ['server', pool, 'pool'])
# commit changes
self.cli_commit()
# Check generated configuration
- tmp = get_config_value('server')
- for server in servers:
- test = f'{server} iburst ' + ' '.join(options)
- self.assertTrue(test in tmp)
+ config = read_file(NTP_CONF)
+ self.assertIn('driftfile /var/lib/ntp/ntp.drift', config)
+ self.assertIn('restrict default noquery nopeer notrap nomodify', config)
+ self.assertIn('restrict source nomodify notrap noquery', config)
+ self.assertIn('restrict 127.0.0.1', config)
+ self.assertIn('restrict -6 ::1', config)
- tmp = get_config_value('pool')
- self.assertTrue(f'{ntp_pool} iburst' in tmp)
+ for server in servers:
+ self.assertIn(f'server {server} iburst ' + ' '.join(options), config)
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ for pool in pools:
+ self.assertIn(f'pool {pool} iburst', config)
- def test_ntp_clients(self):
+ def test_02_ntp_clients(self):
# Test the allowed-networks statement
listen_address = ['127.0.0.1', '::1']
for listen in listen_address:
@@ -96,23 +95,18 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Check generated client address configuration
+ config = read_file(NTP_CONF)
+ self.assertIn('restrict default ignore', config)
+
for network in networks:
network_address = address_from_cidr(network)
network_netmask = netmask_from_cidr(network)
-
- tmp = get_config_value(f'restrict {network_address}')[0]
- test = f'mask {network_netmask} nomodify notrap nopeer'
- self.assertTrue(tmp in test)
+ self.assertIn(f'restrict {network_address} mask {network_netmask} nomodify notrap nopeer', config)
# Check listen address
- tmp = get_config_value('interface')
- test = ['ignore wildcard']
+ self.assertIn('interface ignore wildcard', config)
for listen in listen_address:
- test.append(f'listen {listen}')
- self.assertEqual(tmp, test)
-
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertIn(f'interface listen {listen}', config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index c65ef9540..aabf2bdf5 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -35,6 +35,7 @@ airbag.enable()
conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf'
sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf'
+nftables_ct_file = r'/run/nftables-ct.conf'
# Every ALG (Application Layer Gateway) consists of either a Kernel Object
# also called a Kernel Module/Driver or some rules present in iptables
@@ -81,16 +82,35 @@ def get_config(config=None):
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
default_values = defaults(base)
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if 'timeout' in default_values and 'custom' in default_values['timeout']:
+ del default_values['timeout']['custom']
conntrack = dict_merge(default_values, conntrack)
return conntrack
def verify(conntrack):
+ if dict_search('ignore.rule', conntrack) != None:
+ for rule, rule_config in conntrack['ignore']['rule'].items():
+ if dict_search('destination.port', rule_config) or \
+ dict_search('source.port', rule_config):
+ if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
+ raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
+
return None
def generate(conntrack):
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.tmpl', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.tmpl', conntrack)
+ render(nftables_ct_file, 'conntrack/nftables-ct.tmpl', conntrack)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft -c -f {nftables_ct_file}')
+ if tmp > 0:
+ if os.path.exists(nftables_ct_file):
+ os.unlink(nftables_ct_file)
+ raise ConfigError('Configuration file errors encountered!')
return None
@@ -127,6 +147,9 @@ def apply(conntrack):
if not find_nftables_ct_rule(rule):
cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
+ # Load new nftables ruleset
+ cmd(f'nft -f {nftables_ct_file}')
+
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
resync_conntrackd()
diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py
index 2e14e0b25..26c50cab6 100755
--- a/src/conf_mode/containers.py
+++ b/src/conf_mode/containers.py
@@ -298,7 +298,7 @@ def apply(container):
f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {port} {volume} {env_opt}'
if 'allow_host_networks' in container_config:
- _cmd(f'{container_base_cmd} --net host {image}')
+ run(f'{container_base_cmd} --net host {image}')
else:
for network in container_config['network']:
ipparam = ''
@@ -306,19 +306,25 @@ def apply(container):
address = container_config['network'][network]['address']
ipparam = f'--ip {address}'
- counter = 0
- while True:
- if counter >= 10:
- break
- try:
- _cmd(f'{container_base_cmd} --net {network} {ipparam} {image}')
- break
- except:
- counter = counter +1
- sleep(0.5)
+ run(f'{container_base_cmd} --net {network} {ipparam} {image}')
return None
+def run(container_cmd):
+ counter = 0
+ while True:
+ if counter >= 10:
+ break
+ try:
+ _cmd(container_cmd)
+ break
+ except:
+ counter = counter +1
+ sleep(0.5)
+
+ return None
+
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 75382034f..82223d60b 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
from glob import glob
from json import loads
@@ -22,6 +23,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
from vyos.template import render
from vyos.util import cmd
@@ -33,7 +35,10 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
+
nftables_conf = '/run/nftables.conf'
+nftables_defines_conf = '/run/nftables_defines.conf'
sysfs_config = {
'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
@@ -97,6 +102,35 @@ def get_firewall_interfaces(conf):
out.update(find_interfaces(iftype_conf))
return out
+def get_firewall_zones(conf):
+ used_v4 = []
+ used_v6 = []
+ zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if 'zone' in zone_policy:
+ for zone, zone_conf in zone_policy['zone'].items():
+ if 'from' in zone_conf:
+ for from_zone, from_conf in zone_conf['from'].items():
+ name = dict_search_args(from_conf, 'firewall', 'name')
+ if name:
+ used_v4.append(name)
+
+ ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+ if ipv6_name:
+ used_v6.append(ipv6_name)
+
+ if 'intra_zone_filtering' in zone_conf:
+ name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name')
+ if name:
+ used_v4.append(name)
+
+ ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name')
+ if ipv6_name:
+ used_v6.append(ipv6_name)
+
+ return {'name': used_v4, 'ipv6_name': used_v6}
+
def get_config(config=None):
if config:
conf = config
@@ -104,16 +138,15 @@ def get_config(config=None):
conf = Config()
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)
default_values = defaults(base)
firewall = dict_merge(default_values, firewall)
+ firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
firewall['interfaces'] = get_firewall_interfaces(conf)
+ firewall['zone_policy'] = get_firewall_zones(conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
diff = get_config_diff(conf)
@@ -121,6 +154,7 @@ def get_config(config=None):
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):
@@ -139,6 +173,17 @@ def verify_rule(firewall, rule_conf, ipv6):
if not {'count', 'time'} <= set(rule_conf['recent']):
raise ConfigError('Recent "count" and "time" values must be defined')
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
for side in ['destination', 'source']:
if side in rule_conf:
side_conf = rule_conf[side]
@@ -150,17 +195,16 @@ def verify_rule(firewall, rule_conf, ipv6):
for group in valid_groups:
if group in side_conf['group']:
group_name = side_conf['group'][group]
-
fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ error_group = fw_group.replace("_", "-")
+ group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
- 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("_", "-")
+ if group_obj is None:
raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+ if not group_obj:
+ print(f'WARNING: {error_group} "{group_name}" has no members')
+
if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
if 'protocol' not in rule_conf:
raise ConfigError('Protocol must be defined if specifying a port or port-group')
@@ -169,10 +213,6 @@ def verify_rule(firewall, rule_conf, ipv6):
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')
@@ -201,8 +241,23 @@ def verify(firewall):
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}')
+ for fw_name, used_names in firewall['zone_policy'].items():
+ for name in used_names:
+ if not dict_search_args(firewall, fw_name, name):
+ raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
+
return None
+def cleanup_rule(table, jump_chain):
+ commands = []
+ results = cmd(f'nft -a list table {table}').split("\n")
+ for line in results:
+ if f'jump {jump_chain}' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
+ return commands
+
def cleanup_commands(firewall):
commands = []
for table in ['ip filter', 'ip6 filter']:
@@ -225,6 +280,7 @@ def cleanup_commands(firewall):
elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain):
commands.append(f'flush chain {table} {chain}')
else:
+ commands += cleanup_rule(table, chain)
commands.append(f'delete chain {table} {chain}')
elif 'rule' in item:
rule = item['rule']
@@ -243,6 +299,7 @@ def generate(firewall):
firewall['cleanup_commands'] = cleanup_commands(firewall)
render(nftables_conf, 'firewall/nftables.tmpl', firewall)
+ render(nftables_defines_conf, 'firewall/nftables-defines.tmpl', firewall)
return None
def apply_sysfs(firewall):
@@ -306,6 +363,12 @@ def state_policy_rule_exists():
search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD')
return 'VYOS_STATE_POLICY' in search_str
+def resync_policy_route():
+ # Update policy route as firewall groups were updated
+ tmp = run(policy_route_conf_script)
+ if tmp > 0:
+ print('Warning: Failed to re-apply policy route configuration')
+
def apply(firewall):
if 'first_install' in firewall:
run('nfct helper add rpc inet tcp')
@@ -325,6 +388,9 @@ def apply(firewall):
apply_sysfs(firewall)
+ if firewall['policy_resync']:
+ resync_policy_route()
+
post_apply_trap(firewall)
return None
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 96f8f6fb6..9f319fc8a 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -28,6 +28,7 @@ from vyos.configdict import dict_merge
from vyos.template import render
from vyos.template import is_ip_network
from vyos.util import cmd
+from vyos.util import run
from vyos.util import check_kmod
from vyos.util import dict_search
from vyos.validate import is_addr_assigned
@@ -179,12 +180,19 @@ def verify(nat):
return None
def generate(nat):
- render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat,
- permission=0o755)
+ render(nftables_nat_config, 'firewall/nftables-nat.tmpl', nat)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft -c -f {nftables_nat_config}')
+ if tmp > 0:
+ if os.path.exists(nftables_ct_file):
+ os.unlink(nftables_ct_file)
+ raise ConfigError('Configuration file errors encountered!')
+
return None
def apply(nat):
- cmd(f'{nftables_nat_config}')
+ cmd(f'nft -f {nftables_nat_config}')
if os.path.isfile(nftables_nat_config):
os.unlink(nftables_nat_config)
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
index e81135a74..1108aebe6 100755
--- a/src/conf_mode/policy-route-interface.py
+++ b/src/conf_mode/policy-route-interface.py
@@ -52,7 +52,7 @@ def verify(if_policy):
if not if_policy:
return None
- for route in ['route', 'ipv6_route']:
+ for route in ['route', 'route6']:
if route in if_policy:
if route not in if_policy['policy']:
raise ConfigError('Policy route not configured')
@@ -71,7 +71,7 @@ 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 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
@@ -98,8 +98,8 @@ def apply(if_policy):
else:
cleanup_rule('ip mangle', route_chain, ifname)
- if 'ipv6_route' in if_policy:
- name = 'VYOS_PBR6_' + if_policy['ipv6_route']
+ if 'route6' in if_policy:
+ name = 'VYOS_PBR6_' + if_policy['route6']
rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name)
if not rule_exists:
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index d098be68d..ee5197af0 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
from json import loads
from sys import exit
@@ -31,6 +32,35 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
+preserve_chains = [
+ 'VYOS_PBR_PREROUTING',
+ 'VYOS_PBR_POSTROUTING',
+ 'VYOS_PBR6_PREROUTING',
+ 'VYOS_PBR6_POSTROUTING'
+]
+
+valid_groups = [
+ 'address_group',
+ 'network_group',
+ 'port_group'
+]
+
+def get_policy_interfaces(conf):
+ out = {}
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ def find_interfaces(iftype_conf, output={}, prefix=''):
+ for ifname, if_conf in iftype_conf.items():
+ if 'policy' in if_conf:
+ output[prefix + ifname] = if_conf['policy']
+ for vif in ['vif', 'vif_s', 'vif_c']:
+ if vif in if_conf:
+ output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
+ return output
+ for iftype, iftype_conf in interfaces.items():
+ out.update(find_interfaces(iftype_conf))
+ return out
+
def get_config(config=None):
if config:
conf = config
@@ -38,67 +68,142 @@ def get_config(config=None):
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)
+ policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ policy['interfaces'] = get_policy_interfaces(conf)
+
return policy
-def verify(policy):
- # bail out early - looks like removal from running config
- if not policy:
- return None
+def verify_rule(policy, name, rule_conf, ipv6):
+ icmp = 'icmp' if not ipv6 else 'icmpv6'
+ if icmp in rule_conf:
+ icmp_defined = False
+ if 'type_name' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name')
+ if 'code' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'type' not in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined')
+ if 'type' in rule_conf[icmp]:
+ icmp_defined = True
+
+ if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP')
+
+ if 'set' in rule_conf:
+ if 'tcp_mss' in rule_conf['set']:
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if not tcp_flags or 'syn' not in tcp_flags:
+ raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
+
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ if dict_search_args(rule_conf, 'protocol') != 'tcp':
+ raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+ not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
+ if not_flags:
+ duplicates = [flag for flag in tcp_flags if flag in not_flags]
+ if duplicates:
+ raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ side_conf = rule_conf[side]
+
+ if 'group' in side_conf:
+ if {'address_group', 'network_group'} <= set(side_conf['group']):
+ raise ConfigError('Only one address-group or network-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+ fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ error_group = fw_group.replace("_", "-")
+ group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule')
+
+ if not group_obj:
+ print(f'WARNING: {error_group} "{group_name}" has no members')
+
+ if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
+ if 'protocol' not in rule_conf:
+ raise ConfigError('Protocol must be defined if specifying a port or port-group')
+
+ if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
+ raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
- for route in ['route', 'ipv6_route']:
+def verify(policy):
+ for route in ['route', 'route6']:
+ ipv6 = route == 'route6'
if route in policy:
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
- for rule_id, rule_conf in pol_conf.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')
+ for rule_id, rule_conf in pol_conf['rule'].items():
+ verify_rule(policy, name, rule_conf, ipv6)
+
+ for ifname, if_policy in policy['interfaces'].items():
+ name = dict_search_args(if_policy, 'route')
+ ipv6_name = dict_search_args(if_policy, 'route6')
+ if name and not dict_search_args(policy, 'route', name):
+ raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name):
+ raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}')
return None
-def generate(policy):
- if not policy:
- if os.path.exists(nftables_conf):
- os.unlink(nftables_conf)
- return None
+def cleanup_rule(table, jump_chain):
+ commands = []
+ results = cmd(f'nft -a list table {table}').split("\n")
+ for line in results:
+ if f'jump {jump_chain}' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ commands.append(f'delete rule {table} {chain} handle {handle_search[1]}')
+ return commands
+
+def cleanup_commands(policy):
+ commands = []
+ for table in ['ip mangle', 'ip6 mangle']:
+ json_str = cmd(f'nft -j list table {table}')
+ obj = loads(json_str)
+ if 'nftables' not in obj:
+ continue
+ for item in obj['nftables']:
+ if 'chain' in item:
+ chain = item['chain']['name']
+ if not chain.startswith("VYOS_PBR"):
+ continue
+ if chain not in preserve_chains:
+ if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands += cleanup_rule(table, chain)
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+def generate(policy):
if not os.path.exists(nftables_conf):
policy['first_install'] = True
+ else:
+ policy['cleanup_commands'] = cleanup_commands(policy)
render(nftables_conf, 'firewall/nftables-policy.tmpl', policy)
return None
def apply_table_marks(policy):
- for route in ['route', 'ipv6_route']:
+ for route in ['route', 'route6']:
if route in policy:
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
@@ -124,14 +229,6 @@ def cleanup_table_marks():
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')
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index a1e7a7286..8a972b9fe 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,6 +22,7 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.ifconfig import Section
from vyos.template import render
from vyos.util import call
from vyos.util import chown
@@ -42,6 +43,24 @@ systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d'
systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf'
+def get_interfaces(type='', vlan=True):
+ """
+ Get interfaces
+ get_interfaces()
+ ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
+
+ get_interfaces("dummy")
+ ['dum0']
+ """
+ interfaces = []
+ ifaces = Section.interfaces(type)
+ for iface in ifaces:
+ if vlan == False and '.' in iface:
+ continue
+ interfaces.append(iface)
+
+ return interfaces
+
def get_nft_filter_chains():
"""
Get nft chains for table filter
@@ -57,6 +76,7 @@ def get_nft_filter_chains():
return chain_list
+
def get_config(config=None):
if config:
@@ -75,8 +95,9 @@ def get_config(config=None):
default_values = defaults(base)
monitoring = dict_merge(default_values, monitoring)
- monitoring['nft_chains'] = get_nft_filter_chains()
monitoring['custom_scripts_dir'] = custom_scripts_dir
+ monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False)
+ monitoring['nft_chains'] = get_nft_filter_chains()
return monitoring
diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
index 0f5e366cd..0c7474156 100755
--- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
+++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py
@@ -1,47 +1,88 @@
#!/usr/bin/env python3
-import subprocess
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+
import time
-def status_to_int(status):
- switcher={
- 'u':'0',
- 'D':'1',
- 'A':'2'
- }
- return switcher.get(status,"")
-
-def description_check(line):
- desc=" ".join(line[3:])
- if desc == "":
+def get_interfaces(type='', vlan=True):
+ """
+ Get interfaces:
+ ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0']
+ """
+ interfaces = []
+ ifaces = Section.interfaces(type)
+ for iface in ifaces:
+ if vlan == False and '.' in iface:
+ continue
+ interfaces.append(iface)
+
+ return interfaces
+
+def get_interface_addresses(iface, link_local_v6=False):
+ """
+ Get IP and IPv6 addresses from interface in one string
+ By default don't get IPv6 link-local addresses
+ If interface doesn't have address, return "-"
+ """
+ addresses = []
+ addrs = Interface(iface).get_addr()
+
+ for addr in addrs:
+ if link_local_v6 == False:
+ if addr.startswith('fe80::'):
+ continue
+ addresses.append(addr)
+
+ if not addresses:
+ return "-"
+
+ return (" ".join(addresses))
+
+def get_interface_description(iface):
+ """
+ Get interface description
+ If none return "empty"
+ """
+ description = Interface(iface).get_alias()
+
+ if not description:
return "empty"
+
+ return description
+
+def get_interface_admin_state(iface):
+ """
+ Interface administrative state
+ up => 0, down => 2
+ """
+ state = Interface(iface).get_admin_state()
+ if state == 'up':
+ admin_state = 0
+ if state == 'down':
+ admin_state = 2
+
+ return admin_state
+
+def get_interface_oper_state(iface):
+ """
+ Interface operational state
+ up => 0, down => 1
+ """
+ state = Interface(iface).operational.get_state()
+ if state == 'down':
+ oper_state = 1
else:
- return desc
-
-def gen_ip_list(index,interfaces):
- line=interfaces[index].split()
- ip_list=line[1]
- if index < len(interfaces):
- index += 1
- while len(interfaces[index].split())==1:
- ip = interfaces[index].split()
- ip_list = ip_list + " " + ip[0]
- index += 1
- if index == len(interfaces):
- break
- return ip_list
-
-interfaces = subprocess.check_output("/usr/libexec/vyos/op_mode/show_interfaces.py --action=show-brief", shell=True).decode('utf-8').splitlines()
-del interfaces[:3]
-lines_count=len(interfaces)
-index=0
-while index<lines_count:
- line=interfaces[index].split()
- if len(line)>1:
- print(f'show_interfaces,interface={line[0]} '
- f'ip_addresses="{gen_ip_list(index,interfaces)}",'
- f'state={status_to_int(line[2][0])}i,'
- f'link={status_to_int(line[2][2])}i,'
- f'description="{description_check(line)}" '
- f'{str(int(time.time()))}000000000')
- index += 1
+ oper_state = 0
+
+ return oper_state
+
+interfaces = get_interfaces()
+
+for iface in interfaces:
+ print(f'show_interfaces,interface={iface} '
+ f'ip_addresses="{get_interface_addresses(iface)}",'
+ f'state={get_interface_admin_state(iface)}i,'
+ f'link={get_interface_oper_state(iface)}i,'
+ f'description="{get_interface_description(iface)}" '
+ f'{str(int(time.time()))}000000000')
diff --git a/src/helpers/strip-private.py b/src/helpers/strip-private.py
index e4e1fe11d..eb584edaf 100755
--- a/src/helpers/strip-private.py
+++ b/src/helpers/strip-private.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -111,6 +111,10 @@ if __name__ == "__main__":
(True, re.compile(r'public-keys \S+'), 'public-keys xxxx@xxx.xxx'),
(True, re.compile(r'type \'ssh-(rsa|dss)\''), 'type ssh-xxx'),
(True, re.compile(r' key \S+'), ' key xxxxxx'),
+ # Strip bucket
+ (True, re.compile(r' bucket \S+'), ' bucket xxxxxx'),
+ # Strip tokens
+ (True, re.compile(r' token \S+'), ' token xxxxxx'),
# Strip OpenVPN secrets
(True, re.compile(r'(shared-secret-key-file|ca-cert-file|cert-file|dh-file|key-file|client) (\S+)'), r'\1 xxxxxx'),
# Strip IPSEC secrets
@@ -123,8 +127,8 @@ if __name__ == "__main__":
# Strip MAC addresses
(args.mac, re.compile(r'([0-9a-fA-F]{2}\:){5}([0-9a-fA-F]{2}((\:{0,1})){3})'), r'xx:xx:xx:xx:xx:\2'),
- # Strip host-name, domain-name, and domain-search
- (args.hostname, re.compile(r'(host-name|domain-name|domain-search) \S+'), r'\1 xxxxxx'),
+ # Strip host-name, domain-name, domain-search and url
+ (args.hostname, re.compile(r'(host-name|domain-name|domain-search|url) \S+'), r'\1 xxxxxx'),
# Strip user-names
(args.username, re.compile(r'(user|username|user-id) \S+'), r'\1 xxxxxx'),
diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2
index 4c6d5ceb8..e2d3fcd33 100755
--- a/src/migration-scripts/bgp/1-to-2
+++ b/src/migration-scripts/bgp/1-to-2
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -20,7 +20,6 @@ from sys import argv
from sys import exit
from vyos.configtree import ConfigTree
-from vyos.template import is_ipv4
if (len(argv) < 1):
print("Must specify file name!")
@@ -51,23 +50,21 @@ if config.exists(base + ['parameters', 'default', 'no-ipv4-unicast']):
# Check if the "default" node is now empty, if so - remove it
if len(config.list_nodes(base + ['parameters'])) == 0:
config.delete(base + ['parameters'])
+else:
+ # As we now install a new default option into BGP we need to migrate all
+ # existing BGP neighbors and restore the old behavior
+ if config.exists(base + ['neighbor']):
+ for neighbor in config.list_nodes(base + ['neighbor']):
+ peer_group = base + ['neighbor', neighbor, 'peer-group']
+ if config.exists(peer_group):
+ peer_group_name = config.return_value(peer_group)
+ # peer group enables old behavior for neighbor - bail out
+ if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']):
+ continue
- exit(0)
-
-# As we now install a new default option into BGP we need to migrate all
-# existing BGP neighbors and restore the old behavior
-if config.exists(base + ['neighbor']):
- for neighbor in config.list_nodes(base + ['neighbor']):
- peer_group = base + ['neighbor', neighbor, 'peer-group']
- if config.exists(peer_group):
- peer_group_name = config.return_value(peer_group)
- # peer group enables old behavior for neighbor - bail out
- if config.exists(base + ['peer-group', peer_group_name, 'address-family', 'ipv4-unicast']):
- continue
-
- afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast']
- if not config.exists(afi_ipv4):
- config.set(afi_ipv4)
+ afi_ipv4 = base + ['neighbor', neighbor, 'address-family', 'ipv4-unicast']
+ if not config.exists(afi_ipv4):
+ config.set(afi_ipv4)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2
index ba10c26f2..a8c930be7 100755
--- a/src/migration-scripts/dns-forwarding/1-to-2
+++ b/src/migration-scripts/dns-forwarding/1-to-2
@@ -16,7 +16,7 @@
#
# This migration script will remove the deprecated 'listen-on' statement
-# from the dns forwarding service and will add the corresponding
+# from the dns forwarding service and will add the corresponding
# listen-address nodes instead. This is required as PowerDNS can only listen
# on interface addresses and not on interface names.
@@ -37,53 +37,50 @@ with open(file_name, 'r') as f:
config = ConfigTree(config_file)
base = ['service', 'dns', 'forwarding']
-if not config.exists(base):
+if not config.exists(base + ['listen-on']):
# Nothing to do
exit(0)
-if config.exists(base + ['listen-on']):
- listen_intf = config.return_values(base + ['listen-on'])
- # Delete node with abandoned command
- config.delete(base + ['listen-on'])
+listen_intf = config.return_values(base + ['listen-on'])
+# Delete node with abandoned command
+config.delete(base + ['listen-on'])
- # retrieve interface addresses for every configured listen-on interface
- listen_addr = []
- for intf in listen_intf:
- # we need to evaluate the interface section before manipulating the 'intf' variable
- section = Interface.section(intf)
- if not section:
- raise ValueError(f'Invalid interface name {intf}')
+# retrieve interface addresses for every configured listen-on interface
+listen_addr = []
+for intf in listen_intf:
+ # we need to evaluate the interface section before manipulating the 'intf' variable
+ section = Interface.section(intf)
+ if not section:
+ raise ValueError(f'Invalid interface name {intf}')
- # we need to treat vif and vif-s interfaces differently,
- # both "real interfaces" use dots for vlan identifiers - those
- # need to be exchanged with vif and vif-s identifiers
- if intf.count('.') == 1:
- # this is a regular VLAN interface
- intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1]
- elif intf.count('.') == 2:
- # this is a QinQ VLAN interface
- intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
-
- # retrieve corresponding interface addresses in CIDR format
- # those need to be converted in pure IP addresses without network information
- path = ['interfaces', section, intf, 'address']
- try:
- for addr in config.return_values(path):
- listen_addr.append( ip_interface(addr).ip )
- except:
- # Some interface types do not use "address" option (e.g. OpenVPN)
- # and may not even have a fixed address
- print("Could not retrieve the address of the interface {} from the config".format(intf))
- print("You will need to update your DNS forwarding configuration manually")
-
- for addr in listen_addr:
- config.set(base + ['listen-address'], value=addr, replace=False)
+ # we need to treat vif and vif-s interfaces differently,
+ # both "real interfaces" use dots for vlan identifiers - those
+ # need to be exchanged with vif and vif-s identifiers
+ if intf.count('.') == 1:
+ # this is a regular VLAN interface
+ intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1]
+ elif intf.count('.') == 2:
+ # this is a QinQ VLAN interface
+ intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
+ # retrieve corresponding interface addresses in CIDR format
+ # those need to be converted in pure IP addresses without network information
+ path = ['interfaces', section, intf, 'address']
try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
- except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- exit(1)
+ for addr in config.return_values(path):
+ listen_addr.append( ip_interface(addr).ip )
+ except:
+ # Some interface types do not use "address" option (e.g. OpenVPN)
+ # and may not even have a fixed address
+ print("Could not retrieve the address of the interface {} from the config".format(intf))
+ print("You will need to update your DNS forwarding configuration manually")
-exit(0)
+for addr in listen_addr:
+ config.set(base + ['listen-address'], value=addr, replace=False)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
index 4a4097d56..bc0b19325 100755
--- a/src/migration-scripts/firewall/6-to-7
+++ b/src/migration-scripts/firewall/6-to-7
@@ -17,6 +17,7 @@
# 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
+# T4178: Update tcp flags to use multi value node
from sys import argv
from sys import exit
@@ -45,6 +46,7 @@ if config.exists(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']
+ rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_time + ['monthdays']):
config.delete(rule_time + ['monthdays'])
@@ -52,11 +54,21 @@ if config.exists(base + ['name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
if config.exists(base + ['ipv6-name']):
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']
+ rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_time + ['monthdays']):
config.delete(rule_time + ['monthdays'])
@@ -64,6 +76,15 @@ if config.exists(base + ['ipv6-name']):
if config.exists(rule_time + ['utc']):
config.delete(rule_time + ['utc'])
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2
new file mode 100755
index 000000000..eebbf9d41
--- /dev/null
+++ b/src/migration-scripts/policy/1-to-2
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T4170: rename "policy ipv6-route" to "policy route6" to match common
+# IPv4/IPv6 schema
+# T4178: Update tcp flags to use multi value node
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['policy', 'ipv6-route']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+config.rename(base, 'route6')
+config.set_tag(['policy', 'route6'])
+
+for route in ['route', 'route6']:
+ route_path = ['policy', route]
+ if config.exists(route_path):
+ for name in config.list_nodes(route_path):
+ if config.exists(route_path + [name, 'rule']):
+ for rule in config.list_nodes(route_path + [name, 'rule']):
+ rule_tcp_flags = route_path + [name, 'rule', rule, 'tcp', 'flags']
+
+ if config.exists(rule_tcp_flags):
+ tmp = config.return_value(rule_tcp_flags)
+ config.delete(rule_tcp_flags)
+ for flag in tmp.split(","):
+ for flag in tmp.split(","):
+ if flag[0] == '!':
+ config.set(rule_tcp_flags + ['not', flag[1:].lower()])
+ else:
+ config.set(rule_tcp_flags + [flag.lower()])
+
+if config.exists(['interfaces']):
+ def if_policy_rename(config, path):
+ if config.exists(path + ['policy', 'ipv6-route']):
+ config.rename(path + ['policy', 'ipv6-route'], 'route6')
+
+ for if_type in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', if_type]):
+ if_path = ['interfaces', if_type, ifname]
+ if_policy_rename(config, if_path)
+
+ for vif_type in ['vif', 'vif-s']:
+ if config.exists(if_path + [vif_type]):
+ for vifname in config.list_nodes(if_path + [vif_type]):
+ if_policy_rename(config, if_path + [vif_type, vifname])
+
+ if config.exists(if_path + [vif_type, vifname, 'vif-c']):
+ for vifcname in config.list_nodes(if_path + [vif_type, vifname, 'vif-c']):
+ if_policy_rename(config, if_path + [vif_type, vifname, 'vif-c', vifcname])
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index cf70890a6..b6bb5b802 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
+import ipaddress
import json
import re
import tabulate
@@ -266,13 +267,17 @@ def show_firewall_group(name=None):
continue
references = find_references(group_type, group_name)
- row = [group_name, group_type, ', '.join(references)]
+ row = [group_name, group_type, '\n'.join(references) or 'N/A']
if 'address' in group_conf:
- row.append(", ".join(group_conf['address']))
+ row.append("\n".join(sorted(group_conf['address'], key=ipaddress.ip_address)))
elif 'network' in group_conf:
- row.append(", ".join(group_conf['network']))
+ row.append("\n".join(sorted(group_conf['network'], key=ipaddress.ip_network)))
+ elif 'mac_address' in group_conf:
+ row.append("\n".join(sorted(group_conf['mac_address'])))
elif 'port' in group_conf:
- row.append(", ".join(group_conf['port']))
+ row.append("\n".join(sorted(group_conf['port'])))
+ else:
+ row.append('N/A')
rows.append(row)
if rows:
@@ -302,7 +307,7 @@ def show_summary():
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])
+ v6_out.append([name, description, interfaces or 'N/A'])
if v6_out:
print('\nIPv6 name:\n')
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
index e0b4ac514..5be40082f 100755
--- a/src/op_mode/policy_route.py
+++ b/src/op_mode/policy_route.py
@@ -26,7 +26,7 @@ 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']
+ routes = ['route', 'route6']
def parse_if(ifname, if_conf):
if 'policy' in if_conf:
@@ -52,7 +52,7 @@ def get_policy_interfaces(conf, policy, name=None, ipv6=False):
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]
+ config_path += ['route6' if ipv6 else 'route', name]
policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
@@ -64,8 +64,8 @@ def get_config_policy(conf, name=None, ipv6=False, interfaces=True):
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():
+ if 'route6' in policy:
+ for route_name, route_conf in policy['route6'].items():
route_conf['interface'] = []
get_policy_interfaces(conf, policy, name, ipv6)
@@ -151,8 +151,8 @@ def show_policy(ipv6=False):
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():
+ if ipv6 and 'route6' in policy:
+ for route, route_conf in policy['route6'].items():
output_policy_route(route, route_conf, ipv6=True)
def show_policy_name(name, ipv6=False):
diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py
new file mode 100755
index 000000000..377180dec
--- /dev/null
+++ b/src/op_mode/show_virtual_server.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import call
+
+def is_configured():
+ """ Check if high-availability virtual-server is configured """
+ config = ConfigTreeQuery()
+ if not config.exists(['high-availability', 'virtual-server']):
+ return False
+ return True
+
+if __name__ == '__main__':
+
+ if is_configured() == False:
+ print('Virtual server not configured!')
+ exit(0)
+
+ call('sudo ipvsadm --list --numeric')
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 2c1db20bf..dab146d28 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018 VyOS maintainers and contributors
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -23,6 +23,7 @@ import tabulate
import vyos.util
+from vyos.configquery import ConfigTreeQuery
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.vrrp import VRRPError, VRRPNoData
@@ -35,7 +36,17 @@ group.add_argument("-d", "--data", action="store_true", help="Print detailed VRR
args = parser.parse_args()
+def is_configured():
+ """ Check if VRRP is configured """
+ config = ConfigTreeQuery()
+ if not config.exists(['high-availability', 'vrrp', 'group']):
+ return False
+ return True
+
# Exit early if VRRP is dead or not configured
+if is_configured() == False:
+ print('VRRP not configured!')
+ exit(0)
if not VRRP.is_running():
print('VRRP is not running')
sys.exit(0)
diff --git a/src/validators/ip-address b/src/validators/ip-address
index 51fb72c85..11d6df09e 100755
--- a/src/validators/ip-address
+++ b/src/validators/ip-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr
index 987bf84ca..60d2ac295 100755
--- a/src/validators/ip-cidr
+++ b/src/validators/ip-cidr
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-cidr $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP CIDR"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-host b/src/validators/ip-host
index f2906e8cf..77c578fa2 100755
--- a/src/validators/ip-host
+++ b/src/validators/ip-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix
index e58aad395..e5a64fea8 100755
--- a/src/validators/ip-prefix
+++ b/src/validators/ip-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-any-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IP prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ip-protocol b/src/validators/ip-protocol
index 7898fa6d0..c4c882502 100755
--- a/src/validators/ip-protocol
+++ b/src/validators/ip-protocol
@@ -38,4 +38,5 @@ if __name__ == '__main__':
if re.match(pattern, input):
exit(0)
+ print(f'Error: {input} is not a valid IP protocol')
exit(1)
diff --git a/src/validators/ipv4 b/src/validators/ipv4
index 53face090..8676d5800 100755
--- a/src/validators/ipv4
+++ b/src/validators/ipv4
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4 $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not IPv4"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address
index 872a7645a..058db088b 100755
--- a/src/validators/ipv4-address
+++ b/src/validators/ipv4-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host
index f42feffa4..74b8c36a7 100755
--- a/src/validators/ipv4-host
+++ b/src/validators/ipv4-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast
index 5465c728d..3f28c51db 100755
--- a/src/validators/ipv4-multicast
+++ b/src/validators/ipv4-multicast
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 multicast address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix
index 8ec8a2c45..7e1e0e8dd 100755
--- a/src/validators/ipv4-prefix
+++ b/src/validators/ipv4-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv4-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv4 prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv4-range b/src/validators/ipv4-range
index cc59039f1..6492bfc52 100755
--- a/src/validators/ipv4-range
+++ b/src/validators/ipv4-range
@@ -7,6 +7,11 @@ ip2dec () {
printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
}
+error_exit() {
+ echo "Error: $1 is not a valid IPv4 address range"
+ exit 1
+}
+
# Only run this if there is a hypen present in $1
if [[ "$1" =~ "-" ]]; then
# This only works with real bash (<<<) - split IP addresses into array with
@@ -15,21 +20,21 @@ if [[ "$1" =~ "-" ]]; then
ipaddrcheck --is-ipv4-single ${strarr[0]}
if [ $? -gt 0 ]; then
- exit 1
+ error_exit $1
fi
ipaddrcheck --is-ipv4-single ${strarr[1]}
if [ $? -gt 0 ]; then
- exit 1
+ error_exit $1
fi
start=$(ip2dec ${strarr[0]})
stop=$(ip2dec ${strarr[1]})
if [ $start -ge $stop ]; then
- exit 1
+ error_exit $1
fi
exit 0
fi
-exit 1
+error_exit $1
diff --git a/src/validators/ipv6 b/src/validators/ipv6
index f18d4a63e..4ae130eb5 100755
--- a/src/validators/ipv6
+++ b/src/validators/ipv6
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6 $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not IPv6"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address
index e5d68d756..1fca77668 100755
--- a/src/validators/ipv6-address
+++ b/src/validators/ipv6-address
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host
index f7a745077..7085809a9 100755
--- a/src/validators/ipv6-host
+++ b/src/validators/ipv6-host
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-host $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 host"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast
index 5afc437e5..5aa7d734a 100755
--- a/src/validators/ipv6-multicast
+++ b/src/validators/ipv6-multicast
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 multicast address"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix
index e43616350..890dda723 100755
--- a/src/validators/ipv6-prefix
+++ b/src/validators/ipv6-prefix
@@ -1,3 +1,10 @@
#!/bin/sh
ipaddrcheck --is-ipv6-net $1
+
+if [ $? -gt 0 ]; then
+ echo "Error: $1 is not a valid IPv6 prefix"
+ exit 1
+fi
+
+exit 0 \ No newline at end of file
diff --git a/src/validators/ipv6-range b/src/validators/ipv6-range
index 033b6461b..a3c401281 100755
--- a/src/validators/ipv6-range
+++ b/src/validators/ipv6-range
@@ -11,6 +11,7 @@ if __name__ == '__main__':
if re.search('([a-f0-9:]+:+)+[a-f0-9]+-([a-f0-9:]+:+)+[a-f0-9]+', ipv6_range):
for tmp in ipv6_range.split('-'):
if not is_ipv6(tmp):
+ print(f'Error: {ipv6_range} is not a valid IPv6 range')
sys.exit(1)
sys.exit(0)
diff --git a/src/validators/mac-address-firewall b/src/validators/mac-address-firewall
new file mode 100755
index 000000000..70551f86d
--- /dev/null
+++ b/src/validators/mac-address-firewall
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import sys
+
+pattern = "^!?([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$"
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ sys.exit(1)
+ if not re.match(pattern, sys.argv[1]):
+ sys.exit(1)
+ sys.exit(0)
diff --git a/src/validators/port-multi b/src/validators/port-multi
new file mode 100755
index 000000000..cef371563
--- /dev/null
+++ b/src/validators/port-multi
@@ -0,0 +1,45 @@
+#!/usr/bin/python3
+
+import sys
+import re
+
+from vyos.util import read_file
+
+services_file = '/etc/services'
+
+def get_services():
+ names = []
+ service_data = read_file(services_file, "")
+ for line in service_data.split("\n"):
+ if not line or line[0] == '#':
+ continue
+ names.append(line.split(None, 1)[0])
+ return names
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ ports = sys.argv[1].split(",")
+ services = get_services()
+
+ for port in ports:
+ if port and port[0] == '!':
+ port = port[1:]
+ if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port):
+ port_1, port_2 = port.split('-')
+ if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
+ print(f'Error: {port} is not a valid port range')
+ sys.exit(1)
+ if int(port_1) > int(port_2):
+ print(f'Error: {port} is not a valid port range')
+ sys.exit(1)
+ elif port.isnumeric():
+ if int(port) not in range(1, 65536):
+ print(f'Error: {port} is not a valid port')
+ sys.exit(1)
+ elif port not in services:
+ print(f'Error: {port} is not a valid service name')
+ sys.exit(1)
+ else:
+ sys.exit(2)
+
+ sys.exit(0)
diff --git a/src/validators/port-range b/src/validators/port-range
index abf0b09d5..5468000a7 100755
--- a/src/validators/port-range
+++ b/src/validators/port-range
@@ -3,16 +3,37 @@
import sys
import re
+from vyos.util import read_file
+
+services_file = '/etc/services'
+
+def get_services():
+ names = []
+ service_data = read_file(services_file, "")
+ for line in service_data.split("\n"):
+ if not line or line[0] == '#':
+ continue
+ names.append(line.split(None, 1)[0])
+ return names
+
+def error(port_range):
+ print(f'Error: {port_range} is not a valid port or port range')
+ sys.exit(1)
+
if __name__ == '__main__':
if len(sys.argv)>1:
port_range = sys.argv[1]
- if re.search('[0-9]{1,5}-[0-9]{1,5}', port_range):
- for tmp in port_range.split('-'):
- if int(tmp) not in range(1, 65535):
- sys.exit(1)
- else:
- if int(port_range) not in range(1, 65535):
- sys.exit(1)
+ if re.match('^[0-9]{1,5}-[0-9]{1,5}$', port_range):
+ port_1, port_2 = port_range.split('-')
+ if int(port_1) not in range(1, 65536) or int(port_2) not in range(1, 65536):
+ error(port_range)
+ if int(port_1) > int(port_2):
+ error(port_range)
+ elif port_range.isnumeric() and int(port_range) not in range(1, 65536):
+ error(port_range)
+ elif not port_range.isnumeric() and port_range not in get_services():
+ print(f'Error: {port_range} is not a valid service name')
+ sys.exit(1)
else:
sys.exit(2)
diff --git a/src/validators/tcp-flag b/src/validators/tcp-flag
new file mode 100755
index 000000000..1496b904a
--- /dev/null
+++ b/src/validators/tcp-flag
@@ -0,0 +1,17 @@
+#!/usr/bin/python3
+
+import sys
+import re
+
+if __name__ == '__main__':
+ if len(sys.argv)>1:
+ flag = sys.argv[1]
+ if flag and flag[0] == '!':
+ flag = flag[1:]
+ if flag not in ['syn', 'ack', 'rst', 'fin', 'urg', 'psh', 'ecn', 'cwr']:
+ print(f'Error: {flag} is not a valid TCP flag')
+ sys.exit(1)
+ else:
+ sys.exit(2)
+
+ sys.exit(0)
diff --git a/test-requirements.txt b/test-requirements.txt
index 9348520b5..a475e0a16 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,3 +3,4 @@ lxml
pylint
nose
coverage
+jinja2