diff options
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><MAC address></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><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> <format>!<protocol></format> - <description>IP protocol number</description> + <description>IP protocol name</description> </valueHelp> <constraint> <validator name="ip-protocol"/> @@ -171,6 +176,9 @@ <format>!<MAC address></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>!<MAC address></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>!<MAC address></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><protocol></format> + <description>IP protocol name</description> + </valueHelp> + <valueHelp> + <format>!<protocol></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 |