diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2 index dec027bf9..1975fb9b0 100644 --- a/data/templates/firewall/nftables-bridge.j2 +++ b/data/templates/firewall/nftables-bridge.j2 @@ -1,35 +1,120 @@ +{% import 'firewall/nftables-defines.j2' as group_tmpl %} {% macro bridge(bridge) %} {% set ns = namespace(sets=[]) %} {% if bridge.forward is vyos_defined %} {% for prior, conf in bridge.forward.items() %} chain VYOS_FORWARD_{{ prior }} { type filter hook forward priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('FWD-filter', 'bri') }} } {% endfor %} {% endif %} +{% if bridge.input is vyos_defined %} +{% for prior, conf in bridge.input.items() %} + chain VYOS_INPUT_{{ prior }} { + type filter hook input priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('INP-filter', 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.output is vyos_defined %} +{% for prior, conf in bridge.output.items() %} + chain VYOS_OUTUT_{{ prior }} { + type filter hook output priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('OUT-filter', 'bri') }} + } +{% endfor %} +{% endif %} + {% if bridge.name is vyos_defined %} {% for name_text, conf in bridge.name.items() %} chain NAME_{{ name_text }} { {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'bri') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text, 'bri') }} } {% endfor %} {% endif %} + +{% for set_name in ns.sets %} + set RECENT_{{ set_name }} { + type ipv4_addr + size 65535 + flags dynamic + } +{% endfor %} +{% for set_name in ip_fqdn %} + set FQDN_{{ set_name }} { + type ipv4_addr + flags interval + } +{% endfor %} +{% if geoip_updated.name is vyos_defined %} +{% for setname in geoip_updated.name %} + set {{ setname }} { + type ipv4_addr + flags interval + } +{% endfor %} +{% endif %} + +{{ group_tmpl.groups(group, False, True) }} +{{ group_tmpl.groups(group, True, True) }} + +{% if global_options.state_policy is vyos_defined %} + chain VYOS_STATE_POLICY { +{% if global_options.state_policy.established is vyos_defined %} + {{ global_options.state_policy.established | nft_state_policy('established') }} +{% endif %} +{% if global_options.state_policy.invalid is vyos_defined %} + {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} +{% endif %} +{% if global_options.state_policy.related is vyos_defined %} + {{ global_options.state_policy.related | nft_state_policy('related') }} +{% endif %} + return + } +{% endif %} + {% endmacro %} diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index 8a75ab2d6..fa6cd74c0 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -1,123 +1,123 @@ {% macro groups(group, is_ipv6, is_l3) %} {% if group is vyos_defined %} {% set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %} -{% if group.address_group is vyos_defined and not is_ipv6 and is_l3 %} +{% if group.address_group is vyos_defined and not is_ipv6 %} {% for group_name, group_conf in group.address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A_{{ group_name }} { type {{ ip_type }} flags interval auto-merge {% if group_conf.address is vyos_defined or includes %} elements = { {{ group_conf.address | nft_nested_group(includes, group.address_group, 'address') | join(",") }} } {% endif %} } {% endfor %} {% endif %} -{% if group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %} +{% if group.ipv6_address_group is vyos_defined and is_ipv6 %} {% for group_name, group_conf in group.ipv6_address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A6_{{ group_name }} { type {{ ip_type }} flags interval auto-merge {% if group_conf.address is vyos_defined or includes %} elements = { {{ group_conf.address | nft_nested_group(includes, group.ipv6_address_group, 'address') | join(",") }} } {% endif %} } {% endfor %} {% endif %} {% if group.domain_group is vyos_defined and is_l3 %} {% for name, name_config in group.domain_group.items() %} set D_{{ name }} { type {{ ip_type }} flags interval } {% endfor %} {% endif %} {% if group.mac_group is vyos_defined %} {% for group_name, group_conf in group.mac_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set M_{{ group_name }} { type ether_addr {% if group_conf.mac_address is vyos_defined or includes %} elements = { {{ group_conf.mac_address | nft_nested_group(includes, group.mac_group, 'mac_address') | join(",") }} } {% endif %} } {% endfor %} {% endif %} -{% if group.network_group is vyos_defined and not is_ipv6 and is_l3 %} +{% if group.network_group is vyos_defined and not is_ipv6 %} {% for group_name, group_conf in group.network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N_{{ group_name }} { type {{ ip_type }} flags interval auto-merge {% if group_conf.network is vyos_defined or includes %} elements = { {{ group_conf.network | nft_nested_group(includes, group.network_group, 'network') | join(",") }} } {% endif %} } {% endfor %} {% endif %} -{% if group.ipv6_network_group is vyos_defined and is_ipv6 and is_l3 %} +{% if group.ipv6_network_group is vyos_defined and is_ipv6 %} {% for group_name, group_conf in group.ipv6_network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N6_{{ group_name }} { type {{ ip_type }} flags interval auto-merge {% if group_conf.network is vyos_defined or includes %} elements = { {{ group_conf.network | nft_nested_group(includes, group.ipv6_network_group, 'network') | join(",") }} } {% endif %} } {% endfor %} {% endif %} -{% if group.port_group is vyos_defined and is_l3 %} +{% if group.port_group is vyos_defined %} {% for group_name, group_conf in group.port_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set P_{{ group_name }} { type inet_service flags interval auto-merge {% if group_conf.port is vyos_defined or includes %} elements = { {{ group_conf.port | nft_nested_group(includes, group.port_group, 'port') | join(",") }} } {% endif %} } {% endfor %} {% endif %} {% if group.interface_group is vyos_defined %} {% for group_name, group_conf in group.interface_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set I_{{ group_name }} { type ifname flags interval auto-merge {% if group_conf.interface is vyos_defined or includes %} elements = { {{ group_conf.interface | nft_nested_group(includes, group.interface_group, 'interface') | join(",") }} } {% endif %} } {% endfor %} {% endif %} {% if group.dynamic_group is vyos_defined %} {% if group.dynamic_group.address_group is vyos_defined and not is_ipv6 and is_l3 %} {% for group_name, group_conf in group.dynamic_group.address_group.items() %} set DA_{{ group_name }} { type {{ ip_type }} flags dynamic, timeout } {% endfor %} {% endif %} {% if group.dynamic_group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %} {% for group_name, group_conf in group.dynamic_group.ipv6_address_group.items() %} set DA6_{{ group_name }} { type {{ ip_type }} flags dynamic, timeout } {% endfor %} {% endif %} {% endif %} {% endif %} {% endmacro %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 68a3bfd87..82dcefac0 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -1,345 +1,442 @@ #!/usr/sbin/nft -f {% import 'firewall/nftables-defines.j2' as group_tmpl %} {% import 'firewall/nftables-bridge.j2' as bridge_tmpl %} {% import 'firewall/nftables-offload.j2' as offload_tmpl %} {% import 'firewall/nftables-zone.j2' as zone_tmpl %} flush chain raw vyos_global_rpfilter flush chain ip6 raw vyos_global_rpfilter table raw { chain vyos_global_rpfilter { {% if global_options.source_validation is vyos_defined('loose') %} fib saddr oif 0 counter drop {% elif global_options.source_validation is vyos_defined('strict') %} fib saddr . iif oif 0 counter drop {% endif %} return } } table ip6 raw { chain vyos_global_rpfilter { {% if global_options.ipv6_source_validation is vyos_defined('loose') %} fib saddr oif 0 counter drop {% elif global_options.ipv6_source_validation is vyos_defined('strict') %} fib saddr . iif oif 0 counter drop {% endif %} return } } {% if first_install is not vyos_defined %} delete table ip vyos_filter {% endif %} table ip vyos_filter { {% if ipv4 is vyos_defined %} {% if flowtable is vyos_defined %} {% for name, flowtable_conf in flowtable.items() %} {{ offload_tmpl.flowtable(name, flowtable_conf) }} {% endfor %} {% endif %} {% set ns = namespace(sets=[]) %} {% if ipv4.forward is vyos_defined %} {% for prior, conf in ipv4.forward.items() %} chain VYOS_FORWARD_{{ prior }} { type filter hook forward priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined %} jump VYOS_STATE_POLICY {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('FWD', prior, rule_id) }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('FWD-' + prior, 'ipv4') }} } {% endfor %} {% endif %} {% if ipv4.input is vyos_defined %} {% for prior, conf in ipv4.input.items() %} chain VYOS_INPUT_{{ prior }} { type filter hook input priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined %} jump VYOS_STATE_POLICY {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('INP',prior, rule_id) }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('INP-' + prior, 'ipv4') }} } {% endfor %} {% endif %} {% if ipv4.output is vyos_defined %} {% for prior, conf in ipv4.output.items() %} chain VYOS_OUTPUT_{{ prior }} { type filter hook output priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined and prior == 'filter' %} jump VYOS_STATE_POLICY {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('OUT', prior, rule_id) }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('OUT-' + prior, 'ipv4') }} } {% endfor %} {% endif %} {% if ipv4.prerouting is vyos_defined %} {% for prior, conf in ipv4.prerouting.items() %} chain VYOS_PREROUTING_{{ prior }} { type filter hook prerouting priority {{ prior }}; policy accept; {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('PRE', prior, rule_id) }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['PRE_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('PRE-' + prior, 'ipv4') }} } {% endfor %} {% endif %} chain VYOS_FRAG_MARK { type filter hook prerouting priority -450; policy accept; ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return } {% if ipv4.name is vyos_defined %} {% for name_text, conf in ipv4.name.items() %} chain NAME_{{ name_text }} { {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('NAM', name_text, rule_id) }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text, 'ipv4') }} } {% endfor %} {% endif %} {% for set_name in ns.sets %} set RECENT_{{ set_name }} { type ipv4_addr size 65535 flags dynamic } {% endfor %} {% for set_name in ip_fqdn %} set FQDN_{{ set_name }} { type ipv4_addr flags interval } {% endfor %} {% if geoip_updated.name is vyos_defined %} {% for setname in geoip_updated.name %} set {{ setname }} { type ipv4_addr flags interval } {% endfor %} {% endif %} {% endif %} {{ group_tmpl.groups(group, False, True) }} {% if zone is vyos_defined %} {{ zone_tmpl.zone_chains(zone, False, global_options.state_policy is vyos_defined) }} {% endif %} {% if global_options.state_policy is vyos_defined %} chain VYOS_STATE_POLICY { {% if global_options.state_policy.established is vyos_defined %} {{ global_options.state_policy.established | nft_state_policy('established') }} {% endif %} {% if global_options.state_policy.invalid is vyos_defined %} {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} {% endif %} {% if global_options.state_policy.related is vyos_defined %} {{ global_options.state_policy.related | nft_state_policy('related') }} {% endif %} return } {% endif %} } {% if first_install is not vyos_defined %} delete table ip6 vyos_filter {% endif %} table ip6 vyos_filter { {% if ipv6 is vyos_defined %} {% if flowtable is vyos_defined %} {% for name, flowtable_conf in flowtable.items() %} {{ offload_tmpl.flowtable(name, flowtable_conf) }} {% endfor %} {% endif %} {% set ns = namespace(sets=[]) %} {% if ipv6.forward is vyos_defined %} {% for prior, conf in ipv6.forward.items() %} chain VYOS_IPV6_FORWARD_{{ prior }} { type filter hook forward priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined %} jump VYOS_STATE_POLICY6 {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('FWD', prior, rule_id, 'ip6') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('FWD-' + prior, 'ipv6') }} } {% endfor %} {% endif %} {% if ipv6.input is vyos_defined %} {% for prior, conf in ipv6.input.items() %} chain VYOS_IPV6_INPUT_{{ prior }} { type filter hook input priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined %} jump VYOS_STATE_POLICY6 {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('INP', prior, rule_id, 'ip6') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('INP-' + prior, 'ipv6') }} } {% endfor %} {% endif %} {% if ipv6.output is vyos_defined %} {% for prior, conf in ipv6.output.items() %} chain VYOS_IPV6_OUTPUT_{{ prior }} { type filter hook output priority {{ prior }}; policy accept; {% if global_options.state_policy is vyos_defined and prior == 'filter' %} jump VYOS_STATE_POLICY6 {% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('OUT', prior, rule_id, 'ip6') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['OUT_ ' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('OUT-' + prior, 'ipv6') }} } {% endfor %} {% endif %} {% if ipv6.prerouting is vyos_defined %} {% for prior, conf in ipv6.prerouting.items() %} chain VYOS_IPV6_PREROUTING_{{ prior }} { type filter hook prerouting priority {{ prior }}; policy accept; {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('PRE', prior, rule_id, 'ip6') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['PRE_' + prior + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule('PRE-' + prior, 'ipv6') }} } {% endfor %} {% endif %} chain VYOS_FRAG6_MARK { type filter hook prerouting priority -450; policy accept; exthdr frag exists meta mark set 0xffff1 return } {% if ipv6.name is vyos_defined %} {% for name_text, conf in ipv6.name.items() %} chain NAME6_{{ name_text }} { {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'ip6') }} {% if rule_conf.recent is vyos_defined %} {% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} {% endif %} {% endfor %} {% endif %} {{ conf | nft_default_rule(name_text, 'ipv6') }} } {% endfor %} {% endif %} {% for set_name in ns.sets %} set RECENT6_{{ set_name }} { type ipv6_addr size 65535 flags dynamic } {% endfor %} {% for set_name in ip6_fqdn %} set FQDN_{{ set_name }} { type ipv6_addr flags interval } {% endfor %} {% if geoip_updated.ipv6_name is vyos_defined %} {% for setname in geoip_updated.ipv6_name %} set {{ setname }} { type ipv6_addr flags interval } {% endfor %} {% endif %} {% endif %} {{ group_tmpl.groups(group, True, True) }} {% if zone is vyos_defined %} {{ zone_tmpl.zone_chains(zone, True, global_options.state_policy is vyos_defined) }} {% endif %} {% if global_options.state_policy is vyos_defined %} chain VYOS_STATE_POLICY6 { {% if global_options.state_policy.established is vyos_defined %} {{ global_options.state_policy.established | nft_state_policy('established') }} {% endif %} {% if global_options.state_policy.invalid is vyos_defined %} {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} {% endif %} {% if global_options.state_policy.related is vyos_defined %} {{ global_options.state_policy.related | nft_state_policy('related') }} {% endif %} return } {% endif %} } ## Bridge Firewall {% if first_install is not vyos_defined %} delete table bridge vyos_filter {% endif %} table bridge vyos_filter { -{{ bridge_tmpl.bridge(bridge) }} +{% if bridge is vyos_defined %} +{% if bridge.forward is vyos_defined %} +{% for prior, conf in bridge.forward.items() %} + chain VYOS_FORWARD_{{ prior }} { + type filter hook forward priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('FWD-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.input is vyos_defined %} +{% for prior, conf in bridge.input.items() %} + chain VYOS_INPUT_{{ prior }} { + type filter hook input priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('INP-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.output is vyos_defined %} +{% for prior, conf in bridge.output.items() %} + chain VYOS_OUTUT_{{ prior }} { + type filter hook output priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('OUT-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.prerouting is vyos_defined %} +{% for prior, conf in bridge.prerouting.items() %} + chain VYOS_PREROUTING_{{ prior }} { + type filter hook prerouting priority {{ prior }}; policy accept; +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('PRE', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('PRE-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.name is vyos_defined %} +{% for name_text, conf in bridge.name.items() %} + chain NAME_{{ name_text }} { +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule(name_text, 'bri') }} + } +{% endfor %} +{% endif %} + +{% endif %} {{ group_tmpl.groups(group, False, False) }} +{{ group_tmpl.groups(group, True, False) }} -} +{% if global_options.state_policy is vyos_defined %} + chain VYOS_STATE_POLICY { +{% if global_options.state_policy.established is vyos_defined %} + {{ global_options.state_policy.established | nft_state_policy('established') }} +{% endif %} +{% if global_options.state_policy.invalid is vyos_defined %} + {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} +{% endif %} +{% if global_options.state_policy.related is vyos_defined %} + {{ global_options.state_policy.related | nft_state_policy('related') }} +{% endif %} + return + } +{% endif %} +} \ No newline at end of file diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2 index b9c3311e2..6c33ffdc8 100644 --- a/data/templates/firewall/sysctl-firewall.conf.j2 +++ b/data/templates/firewall/sysctl-firewall.conf.j2 @@ -1,28 +1,36 @@ # Autogenerated by firewall.py # gloabl options net.ipv4.icmp_echo_ignore_all = {{ 0 if global_options.all_ping == 'enable' else 1 }} net.ipv4.icmp_echo_ignore_broadcasts = {{ 0 if global_options.broadcast_ping == 'enable' else 1 }} net.ipv4.conf.all.bc_forwarding = {{ 1 if global_options.directed_broadcast == 'enable' else 0 }} net.ipv4.conf.*.accept_source_route = {{ 1 if global_options.ip_src_route == 'enable' else 0 }} net.ipv6.conf.*.accept_redirects = {{ 1 if global_options.ipv6_receive_redirects == 'enable' else 0 }} net.ipv6.conf.*.accept_source_route = {{ 0 if global_options.ipv6_src_route == 'enable' else -1 }} net.ipv4.conf.all.log_martians = {{ 1 if global_options.log_martians == 'enable' else 0 }} net.ipv4.conf.*.accept_redirects = {{ 1 if global_options.receive_redirects == 'enable' else 0 }} net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enable' else 0 }} net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }} net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }} +{% if global_options.apply_to_bridged_traffic is vyos_defined %} +net.bridge.bridge-nf-call-iptables = {{ 1 if global_options.apply_to_bridged_traffic.ipv4 is vyos_defined else 0 }} +net.bridge.bridge-nf-call-ip6tables = {{ 1 if global_options.apply_to_bridged_traffic.ipv6 is vyos_defined else 0 }} +{% else %} +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0 +{% endif %} + ## Timeout values: net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }} net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }} net.netfilter.nf_conntrack_tcp_timeout_close_wait = {{ global_options.timeout.tcp.close_wait }} net.netfilter.nf_conntrack_tcp_timeout_close = {{ global_options.timeout.tcp.close }} net.netfilter.nf_conntrack_tcp_timeout_established = {{ global_options.timeout.tcp.established }} net.netfilter.nf_conntrack_tcp_timeout_fin_wait = {{ global_options.timeout.tcp.fin_wait }} net.netfilter.nf_conntrack_tcp_timeout_last_ack = {{ global_options.timeout.tcp.last_ack }} net.netfilter.nf_conntrack_tcp_timeout_syn_recv = {{ global_options.timeout.tcp.syn_recv }} net.netfilter.nf_conntrack_tcp_timeout_syn_sent = {{ global_options.timeout.tcp.syn_sent }} net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ global_options.timeout.tcp.time_wait }} net.netfilter.nf_conntrack_udp_timeout = {{ global_options.timeout.udp.other }} net.netfilter.nf_conntrack_udp_timeout_stream = {{ global_options.timeout.udp.stream }} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index dc4625af0..816dd1855 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -1,541 +1,544 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py"> <properties> <priority>319</priority> <help>Firewall</help> </properties> <children> #include <include/firewall/global-options.xml.i> <tagNode name="flowtable"> <properties> <help>Flowtable</help> <constraint> <regex>[a-zA-Z0-9][\w\-\.]*</regex> </constraint> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="interface"> <properties> <help>Interfaces to use this flowtable</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> <multi/> </properties> </leafNode> <leafNode name="offload"> <properties> <help>Offloading method</help> <completionHelp> <list>hardware software</list> </completionHelp> <valueHelp> <format>hardware</format> <description>Hardware offload</description> </valueHelp> <valueHelp> <format>software</format> <description>Software offload</description> </valueHelp> <constraint> <regex>(hardware|software)</regex> </constraint> </properties> <defaultValue>software</defaultValue> </leafNode> </children> </tagNode> <node name="group"> <properties> <help>Firewall group</help> </properties> <children> <tagNode name="address-group"> <properties> <help>Firewall address-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> <leafNode name="address"> <properties> <help>Address-group member</help> <valueHelp> <format>ipv4</format> <description>IPv4 address to match</description> </valueHelp> <valueHelp> <format>ipv4range</format> <description>IPv4 range to match (e.g. 10.0.0.1-10.0.0.200)</description> </valueHelp> <constraint> <validator name="ipv4-address"/> <validator name="ipv4-range"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another address-group</help> <completionHelp> <path>firewall group address-group</path> </completionHelp> <multi/> </properties> </leafNode> #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="domain-group"> <properties> <help>Firewall domain-group</help> <constraint> <regex>[a-zA-Z_][a-zA-Z0-9]?[\w\-\.]*</regex> </constraint> <constraintErrorMessage>Name of domain-group can only contain alphanumeric letters, hyphen, underscores and not start with numeric</constraintErrorMessage> </properties> <children> <leafNode name="address"> <properties> <help>Domain-group member</help> <valueHelp> <format>txt</format> <description>Domain address to match</description> </valueHelp> <constraint> <validator name="fqdn"/> </constraint> <multi/> </properties> </leafNode> #include <include/generic-description.xml.i> </children> </tagNode> <node name="dynamic-group"> <properties> <help>Firewall dynamic group</help> </properties> <children> <tagNode name="address-group"> <properties> <help>Firewall dynamic address group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="ipv6-address-group"> <properties> <help>Firewall dynamic IPv6 address group</help> <constraint> <regex>[a-zA-Z0-9][\w\-\.]*</regex> </constraint> </properties> <children> #include <include/generic-description.xml.i> </children> </tagNode> </children> </node> <tagNode name="interface-group"> <properties> <help>Firewall interface-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> <leafNode name="interface"> <properties> <help>Interface-group member</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> <multi/> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another interface-group</help> <completionHelp> <path>firewall group interface-group</path> </completionHelp> <multi/> </properties> </leafNode> #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="ipv6-address-group"> <properties> <help>Firewall ipv6-address-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> <leafNode name="address"> <properties> <help>Address-group member</help> <valueHelp> <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> </leafNode> <leafNode name="include"> <properties> <help>Include another ipv6-address-group</help> <completionHelp> <path>firewall group ipv6-address-group</path> </completionHelp> <multi/> </properties> </leafNode> #include <include/generic-description.xml.i> </children> </tagNode> <tagNode name="ipv6-network-group"> <properties> <help>Firewall ipv6-network-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="network"> <properties> <help>Network-group member</help> <valueHelp> <format>ipv6net</format> <description>IPv6 address to match</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another ipv6-network-group</help> <completionHelp> <path>firewall group ipv6-network-group</path> </completionHelp> <multi/> </properties> </leafNode> </children> </tagNode> <tagNode name="mac-group"> <properties> <help>Firewall mac-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="mac-address"> <properties> <help>Mac-group member</help> <valueHelp> <format>macaddr</format> <description>MAC address to match</description> </valueHelp> <constraint> <validator name="mac-address"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another mac-group</help> <completionHelp> <path>firewall group mac-group</path> </completionHelp> <multi/> </properties> </leafNode> </children> </tagNode> <tagNode name="network-group"> <properties> <help>Firewall network-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="network"> <properties> <help>Network-group member</help> <valueHelp> <format>ipv4net</format> <description>IPv4 Subnet to match</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another network-group</help> <completionHelp> <path>firewall group network-group</path> </completionHelp> <multi/> </properties> </leafNode> </children> </tagNode> <tagNode name="port-group"> <properties> <help>Firewall port-group</help> <constraint> #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> </constraint> <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> #include <include/generic-description.xml.i> <leafNode name="port"> <properties> <help>Port-group member</help> <valueHelp> <format>txt</format> <description>Named port (any name in /etc/services, e.g., http)</description> </valueHelp> <valueHelp> <format>u32:1-65535</format> <description>Numbered port</description> </valueHelp> <valueHelp> <format>start-end</format> <description>Numbered port range (e.g. 1001-1050)</description> </valueHelp> <multi/> <constraint> <validator name="port-range"/> </constraint> </properties> </leafNode> <leafNode name="include"> <properties> <help>Include another port-group</help> <completionHelp> <path>firewall group port-group</path> </completionHelp> <multi/> </properties> </leafNode> </children> </tagNode> </children> </node> <node name="bridge"> <properties> <help>Bridge firewall</help> </properties> <children> #include <include/firewall/bridge-hook-forward.xml.i> + #include <include/firewall/bridge-hook-input.xml.i> + #include <include/firewall/bridge-hook-output.xml.i> + #include <include/firewall/bridge-hook-prerouting.xml.i> #include <include/firewall/bridge-custom-name.xml.i> </children> </node> <node name="ipv4"> <properties> <help>IPv4 firewall</help> </properties> <children> #include <include/firewall/ipv4-hook-forward.xml.i> #include <include/firewall/ipv4-hook-input.xml.i> #include <include/firewall/ipv4-hook-output.xml.i> #include <include/firewall/ipv4-hook-prerouting.xml.i> #include <include/firewall/ipv4-custom-name.xml.i> </children> </node> <node name="ipv6"> <properties> <help>IPv6 firewall</help> </properties> <children> #include <include/firewall/ipv6-hook-forward.xml.i> #include <include/firewall/ipv6-hook-input.xml.i> #include <include/firewall/ipv6-hook-output.xml.i> #include <include/firewall/ipv6-hook-prerouting.xml.i> #include <include/firewall/ipv6-custom-name.xml.i> </children> </node> <tagNode name="zone"> <properties> <help>Zone-policy</help> <valueHelp> <format>txt</format> <description>Zone name</description> </valueHelp> <constraint> <regex>[a-zA-Z0-9][\w\-\.]*</regex> </constraint> </properties> <children> #include <include/generic-description.xml.i> #include <include/firewall/default-log.xml.i> <leafNode name="default-action"> <properties> <help>Default-action for traffic coming into this zone</help> <completionHelp> <list>drop reject</list> </completionHelp> <valueHelp> <format>drop</format> <description>Drop silently</description> </valueHelp> <valueHelp> <format>reject</format> <description>Drop and notify source</description> </valueHelp> <constraint> <regex>(drop|reject)</regex> </constraint> </properties> <defaultValue>drop</defaultValue> </leafNode> <tagNode name="from"> <properties> <help>Zone from which to filter traffic</help> <completionHelp> <path>firewall zone</path> </completionHelp> </properties> <children> <node name="firewall"> <properties> <help>Firewall options</help> </properties> <children> <leafNode name="ipv6-name"> <properties> <help>IPv6 firewall ruleset</help> <completionHelp> <path>firewall ipv6 name</path> </completionHelp> </properties> </leafNode> <leafNode name="name"> <properties> <help>IPv4 firewall ruleset</help> <completionHelp> <path>firewall ipv4 name</path> </completionHelp> </properties> </leafNode> </children> </node> </children> </tagNode> <leafNode name="interface"> <properties> <help>Interface associated with zone</help> <valueHelp> <format>txt</format> <description>Interface associated with zone</description> </valueHelp> <valueHelp> <format>vrf</format> <description>VRF associated with zone</description> </valueHelp> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> <path>vrf name</path> </completionHelp> <multi/> </properties> </leafNode> <node name="intra-zone-filtering"> <properties> <help>Intra-zone filtering</help> </properties> <children> <leafNode name="action"> <properties> <help>Action for intra-zone traffic</help> <completionHelp> <list>accept drop</list> </completionHelp> <valueHelp> <format>accept</format> <description>Accept traffic</description> </valueHelp> <valueHelp> <format>drop</format> <description>Drop silently</description> </valueHelp> <constraint> <regex>(accept|drop)</regex> </constraint> </properties> </leafNode> <node name="firewall"> <properties> <help>Use the specified firewall chain</help> </properties> <children> <leafNode name="ipv6-name"> <properties> <help>IPv6 firewall ruleset</help> <completionHelp> <path>firewall ipv6 name</path> </completionHelp> </properties> </leafNode> <leafNode name="name"> <properties> <help>IPv4 firewall ruleset</help> <completionHelp> <path>firewall ipv4 name</path> </completionHelp> </properties> </leafNode> </children> </node> </children> </node> <leafNode name="local-zone"> <properties> <help>Zone to be local-zone</help> <valueless/> </properties> </leafNode> </children> </tagNode> </children> </node> </interfaceDefinition> diff --git a/interface-definitions/include/firewall/address-inet.xml.i b/interface-definitions/include/firewall/address-inet.xml.i new file mode 100644 index 000000000..02ed8f6e4 --- /dev/null +++ b/interface-definitions/include/firewall/address-inet.xml.i @@ -0,0 +1,63 @@ +<!-- include start from firewall/address-inet.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Subnet to match</description> + </valueHelp> + <valueHelp> + <format>ipv6range</format> + <description>IP range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + <validator name="ipv6"/> + <validator name="ipv6-exclude"/> + <validator name="ipv6-range"/> + <validator name="ipv6-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> \ No newline at end of file diff --git a/interface-definitions/include/firewall/address-mask-inet.xml.i b/interface-definitions/include/firewall/address-mask-inet.xml.i new file mode 100644 index 000000000..e2a5927ab --- /dev/null +++ b/interface-definitions/include/firewall/address-mask-inet.xml.i @@ -0,0 +1,19 @@ +<!-- include start from firewall/address-mask-inet.xml.i --> +<leafNode name="address-mask"> + <properties> + <help>IP mask</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 mask to apply</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IP mask to apply</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> \ No newline at end of file diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i index 654493c0e..9a2a829d0 100644 --- a/interface-definitions/include/firewall/bridge-custom-name.xml.i +++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i @@ -1,39 +1,45 @@ <!-- include start from firewall/bridge-custom-name.xml.i --> <tagNode name="name"> <properties> <help>Bridge custom firewall</help> <constraint> <regex>[a-zA-Z0-9][\w\-\.]*</regex> </constraint> </properties> <children> #include <include/firewall/default-action.xml.i> #include <include/firewall/default-log.xml.i> #include <include/generic-description.xml.i> <leafNode name="default-jump-target"> <properties> <help>Set jump target. Action jump must be defined in default-action to use this setting</help> <completionHelp> <path>firewall bridge name</path> </completionHelp> </properties> </leafNode> <tagNode name="rule"> <properties> <help>Bridge Firewall forward filter rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number for this firewall rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> </children> </tagNode> <!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i index 99f66ec77..fcc981925 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i @@ -1,35 +1,41 @@ <!-- include start from firewall/bridge-hook-forward.xml.i --> <node name="forward"> <properties> <help>Bridge forward firewall</help> </properties> <children> <node name="filter"> <properties> <help>Bridge firewall forward filter</help> </properties> <children> #include <include/firewall/default-action-base-chains.xml.i> #include <include/firewall/default-log.xml.i> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> <help>Bridge Firewall forward filter rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number for this firewall rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> </children> </node> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-input.xml.i similarity index 64% copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i copy to interface-definitions/include/firewall/bridge-hook-input.xml.i index 99f66ec77..f6a11f8da 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-input.xml.i @@ -1,35 +1,40 @@ -<!-- include start from firewall/bridge-hook-forward.xml.i --> -<node name="forward"> +<!-- include start from firewall/bridge-hook-input.xml.i --> +<node name="input"> <properties> - <help>Bridge forward firewall</help> + <help>Bridge input firewall</help> </properties> <children> <node name="filter"> <properties> - <help>Bridge firewall forward filter</help> + <help>Bridge firewall input filter</help> </properties> <children> #include <include/firewall/default-action-base-chains.xml.i> #include <include/firewall/default-log.xml.i> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> - <help>Bridge Firewall forward filter rule number</help> + <help>Bridge Firewall input filter rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number for this firewall rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> </children> </tagNode> </children> </node> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-output.xml.i similarity index 64% copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i copy to interface-definitions/include/firewall/bridge-hook-output.xml.i index 99f66ec77..38b8b08ca 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-output.xml.i @@ -1,35 +1,40 @@ -<!-- include start from firewall/bridge-hook-forward.xml.i --> -<node name="forward"> +<!-- include start from firewall/bridge-hook-output.xml.i --> +<node name="output"> <properties> - <help>Bridge forward firewall</help> + <help>Bridge output firewall</help> </properties> <children> <node name="filter"> <properties> - <help>Bridge firewall forward filter</help> + <help>Bridge firewall output filter</help> </properties> <children> #include <include/firewall/default-action-base-chains.xml.i> #include <include/firewall/default-log.xml.i> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> - <help>Bridge Firewall forward filter rule number</help> + <help>Bridge Firewall output filter rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number for this firewall rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> </children> </node> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i similarity index 71% copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i copy to interface-definitions/include/firewall/bridge-hook-prerouting.xml.i index 99f66ec77..ea567644f 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i @@ -1,35 +1,37 @@ -<!-- include start from firewall/bridge-hook-forward.xml.i --> -<node name="forward"> +<!-- include start from firewall/bridge-hook-prerouting.xml.i --> +<node name="prerouting"> <properties> - <help>Bridge forward firewall</help> + <help>Bridge prerouting firewall</help> </properties> <children> <node name="filter"> <properties> - <help>Bridge firewall forward filter</help> + <help>Bridge firewall prerouting filter</help> </properties> <children> #include <include/firewall/default-action-base-chains.xml.i> #include <include/firewall/default-log.xml.i> #include <include/generic-description.xml.i> <tagNode name="rule"> <properties> - <help>Bridge Firewall forward filter rule number</help> + <help>Bridge firewall prerouting filter rule number</help> <valueHelp> <format>u32:1-999999</format> <description>Number for this firewall rule</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-999999"/> </constraint> <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-and-notrack.xml.i> + #include <include/firewall/inbound-interface.xml.i> </children> </tagNode> </children> </node> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i index dcdd970ac..9ae28f7be 100644 --- a/interface-definitions/include/firewall/common-rule-bridge.xml.i +++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i @@ -1,34 +1,54 @@ <!-- include start from firewall/common-rule-bridge.xml.i --> -#include <include/firewall/action-l2.xml.i> +#include <include/generic-description.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/firewall-mark.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/firewall/hop-limit.xml.i> +#include <include/firewall/icmp.xml.i> +#include <include/firewall/icmpv6.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/match-ipsec.xml.i> +#include <include/firewall/match-vlan.xml.i> #include <include/firewall/nft-queue.xml.i> +#include <include/firewall/packet-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/time.xml.i> +#include <include/firewall/ttl.xml.i> <node name="destination"> <properties> <help>Destination parameters</help> </properties> <children> #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> </children> </node> -#include <include/generic-disable-node.xml.i> <leafNode name="jump-target"> <properties> <help>Set jump target. Action jump must be defined to use this setting</help> <completionHelp> <path>firewall bridge name</path> </completionHelp> </properties> </leafNode> -#include <include/firewall/log.xml.i> -#include <include/firewall/log-options.xml.i> <node name="source"> <properties> <help>Source parameters</help> </properties> <children> #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> </children> </node> -#include <include/firewall/inbound-interface.xml.i> -#include <include/firewall/outbound-interface.xml.i> -#include <include/firewall/match-vlan.xml.i> <!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index 9039b76fd..cee8f1854 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -1,341 +1,360 @@ <!-- include start from firewall/global-options.xml.i --> <node name="global-options"> <properties> <help>Global Options</help> </properties> <children> <leafNode name="all-ping"> <properties> <help>Policy for handling of all IPv4 ICMP echo requests</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of all IPv4 ICMP echo requests</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of all IPv4 ICMP echo requests</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="broadcast-ping"> <properties> <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> + <node name="apply-to-bridged-traffic"> + <properties> + <help>Apply configured firewall rules to traffic switched by bridges</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>Apply configured IPv4 firewall rules</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>Apply configured IPv6 firewall rules</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> <leafNode name="directed-broadcast"> <properties> <help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable IPv4 directed broadcast forwarding on all interfaces</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable IPv4 directed broadcast forwarding on all interfaces</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="ip-src-route"> <properties> <help>Policy for handling IPv4 packets with source route option</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of IPv4 packets with source route option</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of IPv4 packets with source route option</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="log-martians"> <properties> <help>Policy for logging IPv4 packets with invalid addresses</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable logging of IPv4 packets with invalid addresses</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable logging of Ipv4 packets with invalid addresses</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="receive-redirects"> <properties> <help>Policy for handling received IPv4 ICMP redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of received IPv4 ICMP redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of received IPv4 ICMP redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="resolver-cache"> <properties> <help>Retains last successful value if domain resolution fails</help> <valueless/> </properties> </leafNode> <leafNode name="resolver-interval"> <properties> <help>Domain resolver update interval</help> <valueHelp> <format>u32:10-3600</format> <description>Interval (seconds)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 10-3600"/> </constraint> </properties> <defaultValue>300</defaultValue> </leafNode> <leafNode name="send-redirects"> <properties> <help>Policy for sending IPv4 ICMP redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable sending IPv4 ICMP redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable sending IPv4 ICMP redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <leafNode name="source-validation"> <properties> <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help> <completionHelp> <list>strict loose disable</list> </completionHelp> <valueHelp> <format>strict</format> <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>loose</format> <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>disable</format> <description>No IPv4 source validation</description> </valueHelp> <constraint> <regex>(strict|loose|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <node name="state-policy"> <properties> <help>Global firewall state-policy</help> </properties> <children> <node name="established"> <properties> <help>Global firewall policy for packets part of an established connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> <node name="invalid"> <properties> <help>Global firewall policy for packets part of an invalid connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> <node name="related"> <properties> <help>Global firewall policy for packets part of a related connection</help> </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> </children> </node> <leafNode name="syn-cookies"> <properties> <help>Policy for using TCP SYN cookies with IPv4</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable use of TCP SYN cookies with IPv4</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable use of TCP SYN cookies with IPv4</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>enable</defaultValue> </leafNode> <node name="timeout"> <properties> <help>Connection timeout options</help> </properties> <children> #include <include/firewall/timeout-common-protocols.xml.i> </children> </node> <leafNode name="twa-hazards-protection"> <properties> <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable RFC1337 TIME-WAIT hazards protection</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable RFC1337 TIME-WAIT hazards protection</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-receive-redirects"> <properties> <help>Policy for handling received ICMPv6 redirect messages</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of received ICMPv6 redirect messages</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of received ICMPv6 redirect messages</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-source-validation"> <properties> <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help> <completionHelp> <list>strict loose disable</list> </completionHelp> <valueHelp> <format>strict</format> <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>loose</format> <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description> </valueHelp> <valueHelp> <format>disable</format> <description>No IPv6 source validation</description> </valueHelp> <constraint> <regex>(strict|loose|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> <leafNode name="ipv6-src-route"> <properties> <help>Policy for handling IPv6 packets with routing extension header</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable processing of IPv6 packets with routing header type 2</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable processing of IPv6 packets with routing header</description> </valueHelp> <constraint> <regex>(enable|disable)</regex> </constraint> </properties> <defaultValue>disable</defaultValue> </leafNode> </children> </node> <!-- include end --> diff --git a/interface-definitions/include/firewall/set-packet-modifications.xml.i b/interface-definitions/include/firewall/set-packet-modifications.xml.i new file mode 100644 index 000000000..ee019b64e --- /dev/null +++ b/interface-definitions/include/firewall/set-packet-modifications.xml.i @@ -0,0 +1,96 @@ +<!-- include start from firewall/set-packet-modifications.xml.i --> +<node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="connection-mark"> + <properties> + <help>Set connection mark</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Connection mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="dscp"> + <properties> + <help>Set DSCP (Packet Differentiated Services Codepoint) bits</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mark"> + <properties> + <help>Set packet mark</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Packet mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Set the routing table for matched packets</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <valueHelp> + <format>main</format> + <description>Main table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + <regex>(main)</regex> + </constraint> + <completionHelp> + <list>main</list> + <path>protocols static table</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>VRF to forward packet with</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>Forward into default global VRF</description> + </valueHelp> + <completionHelp> + <list>default</list> + <path>vrf name</path> + </completionHelp> + #include <include/constraint/vrf.xml.i> + </properties> + </leafNode> + <leafNode name="tcp-mss"> + <properties> + <help>Set TCP Maximum Segment Size</help> + <valueHelp> + <format>u32:500-1460</format> + <description>Explicitly set TCP MSS value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-1460"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> \ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-group-inet.xml.i b/interface-definitions/include/firewall/source-destination-group-inet.xml.i new file mode 100644 index 000000000..174051624 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-inet.xml.i @@ -0,0 +1,50 @@ +<!-- include start from firewall/source-destination-group-inet.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="ipv4-address-group"> + <properties> + <help>Group of IPv4 addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-address-group"> + <properties> + <help>Group of IPv6 addresses</help> + <completionHelp> + <path>firewall group ipv6-address-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/firewall/mac-group.xml.i> + <leafNode name="ipv4-network-group"> + <properties> + <help>Group of IPv4 networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-network-group"> + <properties> + <help>Group of IPv6 networks</help> + <completionHelp> + <path>firewall group ipv6-network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i index 203be73e7..19ffc0506 100644 --- a/interface-definitions/include/policy/route-common.xml.i +++ b/interface-definitions/include/policy/route-common.xml.i @@ -1,209 +1,116 @@ <!-- include start from policy/route-common.xml.i --> #include <include/policy/route-rule-action.xml.i> #include <include/generic-description.xml.i> #include <include/firewall/firewall-mark.xml.i> #include <include/generic-disable-node.xml.i> #include <include/firewall/fragment.xml.i> #include <include/firewall/match-ipsec.xml.i> #include <include/firewall/limit.xml.i> #include <include/firewall/log.xml.i> <leafNode name="protocol"> <properties> <help>Protocol to match (protocol name, number, or "all")</help> <completionHelp> <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script> </completionHelp> <valueHelp> <format>all</format> <description>All IP protocols</description> </valueHelp> <valueHelp> <format>tcp_udp</format> <description>Both TCP and UDP</description> </valueHelp> <valueHelp> <format>0-255</format> <description>IP protocol number</description> </valueHelp> <valueHelp> <format>!<protocol></format> <description>IP protocol number</description> </valueHelp> <constraint> <validator name="ip-protocol"/> </constraint> </properties> <defaultValue>all</defaultValue> </leafNode> <node name="recent"> <properties> <help>Parameters for matching recently seen sources</help> </properties> <children> <leafNode name="count"> <properties> <help>Source addresses seen more than N times</help> <valueHelp> <format>u32:1-255</format> <description>Source addresses seen more than N times</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-255"/> </constraint> </properties> </leafNode> <leafNode name="time"> <properties> <help>Source addresses seen in the last N seconds</help> <valueHelp> <format>u32:0-4294967295</format> <description>Source addresses seen in the last N seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> </constraint> </properties> </leafNode> </children> </node> -<node name="set"> - <properties> - <help>Packet modifications</help> - </properties> - <children> - <leafNode name="connection-mark"> - <properties> - <help>Connection marking</help> - <valueHelp> - <format>u32:0-2147483647</format> - <description>Connection marking</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-2147483647"/> - </constraint> - </properties> - </leafNode> - <leafNode name="dscp"> - <properties> - <help>Packet Differentiated Services Codepoint (DSCP)</help> - <valueHelp> - <format>u32:0-63</format> - <description>DSCP number</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-63"/> - </constraint> - </properties> - </leafNode> - <leafNode name="mark"> - <properties> - <help>Packet marking</help> - <valueHelp> - <format>u32:1-2147483647</format> - <description>Packet marking</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-2147483647"/> - </constraint> - </properties> - </leafNode> - <leafNode name="table"> - <properties> - <help>Routing table to forward packet with</help> - <valueHelp> - <format>u32:1-200</format> - <description>Table number</description> - </valueHelp> - <valueHelp> - <format>main</format> - <description>Main table</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-200"/> - <regex>(main)</regex> - </constraint> - <completionHelp> - <list>main</list> - <path>protocols static table</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="vrf"> - <properties> - <help>VRF to forward packet with</help> - <valueHelp> - <format>txt</format> - <description>VRF instance name</description> - </valueHelp> - <valueHelp> - <format>default</format> - <description>Forward into default global VRF</description> - </valueHelp> - <completionHelp> - <list>default</list> - <path>vrf name</path> - </completionHelp> - #include <include/constraint/vrf.xml.i> - </properties> - </leafNode> - <leafNode name="tcp-mss"> - <properties> - <help>TCP Maximum Segment Size</help> - <valueHelp> - <format>u32:500-1460</format> - <description>Explicitly set TCP MSS value</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 500-1460"/> - </constraint> - </properties> - </leafNode> - </children> -</node> +#include <include/firewall/set-packet-modifications.xml.i> #include <include/firewall/state.xml.i> #include <include/firewall/tcp-flags.xml.i> #include <include/firewall/tcp-mss.xml.i> <node name="time"> <properties> <help>Time to match rule</help> </properties> <children> <leafNode name="monthdays"> <properties> <help>Monthdays to match rule on</help> </properties> </leafNode> <leafNode name="startdate"> <properties> <help>Date to start matching rule</help> </properties> </leafNode> <leafNode name="starttime"> <properties> <help>Time of day to start matching rule</help> </properties> </leafNode> <leafNode name="stopdate"> <properties> <help>Date to stop matching rule</help> </properties> </leafNode> <leafNode name="stoptime"> <properties> <help>Time of day to stop matching rule</help> </properties> </leafNode> <leafNode name="utc"> <properties> <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help> <valueless/> </properties> </leafNode> <leafNode name="weekdays"> <properties> <help>Weekdays to match rule on</help> </properties> </leafNode> </children> </node> <!-- include end --> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index facd498ca..cac6d2953 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -1,676 +1,688 @@ # Copyright (C) 2021-2024 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 csv import gzip import os import re from pathlib import Path from socket import AF_INET from socket import AF_INET6 from socket import getaddrinfo from time import strftime from vyos.remote import download from vyos.template import is_ipv4 from vyos.template import render from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import cmd from vyos.utils.process import run from vyos.utils.network import get_vrf_tableid from vyos.defaults import rt_global_table from vyos.defaults import rt_global_vrf # Conntrack def conntrack_required(conf): required_nodes = ['nat', 'nat66', 'load-balancing wan'] for path in required_nodes: if conf.exists(path): return True firewall = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True) for rules, path in dict_search_recursive(firewall, 'rule'): if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()): return True return False # Domain Resolver def fqdn_config_parse(firewall): firewall['ip_fqdn'] = {} firewall['ip6_fqdn'] = {} for domain, path in dict_search_recursive(firewall, 'fqdn'): hook_name = path[1] priority = path[2] fw_name = path[2] rule = path[4] suffix = path[5][0] set_name = f'{hook_name}_{priority}_{rule}_{suffix}' if (path[0] == 'ipv4') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): firewall['ip_fqdn'][set_name] = domain elif (path[0] == 'ipv6') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'): if path[1] == 'name': set_name = f'name6_{priority}_{rule}_{suffix}' firewall['ip6_fqdn'][set_name] = domain def fqdn_resolve(fqdn, ipv6=False): try: res = getaddrinfo(fqdn, None, AF_INET6 if ipv6 else AF_INET) return set(item[4][0] for item in res) except: return None # End Domain Resolver def find_nftables_rule(table, chain, rule_matches=[]): # Find rule in table/chain that matches all criteria and return the handle results = cmd(f'sudo nft --handle list chain {table} {chain}').split("\n") for line in results: if all(rule_match in line for rule_match in rule_matches): handle_search = re.search('handle (\d+)', line) if handle_search: return handle_search[1] return None def remove_nftables_rule(table, chain, handle): cmd(f'sudo nft delete rule {table} {chain} handle {handle}') # Functions below used by template generation def nft_action(vyos_action): if vyos_action == 'accept': return 'return' return vyos_action def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): output = [] if ip_name == 'ip6': def_suffix = '6' family = 'ipv6' else: def_suffix = '' family = 'bri' if ip_name == 'bri' else 'ipv4' if 'state' in rule_conf and rule_conf['state']: states = ",".join([s for s in rule_conf['state']]) if states: output.append(f'ct state {{{states}}}') if 'conntrack_helper' in rule_conf: helper_map = {'h323': ['RAS', 'Q.931'], 'nfs': ['rpc'], 'sqlnet': ['tns']} helper_out = [] for helper in rule_conf['conntrack_helper']: if helper in helper_map: helper_out.extend(helper_map[helper]) else: helper_out.append(helper) if helper_out: helper_str = ','.join(f'"{s}"' for s in helper_out) output.append(f'ct helper {{{helper_str}}}') if 'connection_status' in rule_conf and rule_conf['connection_status']: status = rule_conf['connection_status'] if status['nat'] == 'destination': nat_status = 'dnat' output.append(f'ct status {nat_status}') if status['nat'] == 'source': nat_status = 'snat' output.append(f'ct status {nat_status}') if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': proto = rule_conf['protocol'] operator = '' if proto[0] == '!': operator = '!=' proto = proto[1:] if proto == 'tcp_udp': proto = '{tcp, udp}' output.append(f'meta l4proto {operator} {proto}') for side in ['destination', 'source']: if side in rule_conf: prefix = side[0] side_conf = rule_conf[side] address_mask = side_conf.get('address_mask', None) if 'address' in side_conf: suffix = side_conf['address'] operator = '' exclude = suffix[0] == '!' if exclude: operator = '!= ' suffix = suffix[1:] if address_mask: operator = '!=' if exclude else '==' operator = f'& {address_mask} {operator} ' - output.append(f'{ip_name} {prefix}addr {operator}{suffix}') + if is_ipv4(suffix): + output.append(f'ip {prefix}addr {operator}{suffix}') + else: + output.append(f'ip6 {prefix}addr {operator}{suffix}') if 'fqdn' in side_conf: fqdn = side_conf['fqdn'] hook_name = '' operator = '' if fqdn[0] == '!': operator = '!=' if hook == 'FWD': hook_name = 'forward' if hook == 'INP': hook_name = 'input' if hook == 'OUT': hook_name = 'output' if hook == 'PRE': hook_name = 'prerouting' if hook == 'NAM': hook_name = f'name{def_suffix}' output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{hook_name}_{fw_name}_{rule_id}_{prefix}') if dict_search_args(side_conf, 'geoip', 'country_code'): operator = '' hook_name = '' if dict_search_args(side_conf, 'geoip', 'inverse_match') != None: operator = '!=' if hook == 'FWD': hook_name = 'forward' if hook == 'INP': hook_name = 'input' if hook == 'OUT': hook_name = 'output' if hook == 'PRE': hook_name = 'prerouting' if hook == 'NAM': hook_name = f'name' output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC{def_suffix}_{hook_name}_{fw_name}_{rule_id}') if 'mac_address' in side_conf: suffix = side_conf["mac_address"] if suffix[0] == '!': suffix = f'!= {suffix[1:]}' output.append(f'ether {prefix}addr {suffix}') if 'port' in side_conf: proto = rule_conf['protocol'] port = side_conf['port'].split(',') ports = [] negated_ports = [] for p in port: if p[0] == '!': negated_ports.append(p[1:]) else: ports.append(p) if proto == 'tcp_udp': proto = 'th' if ports: ports_str = ','.join(ports) output.append(f'{proto} {prefix}port {{{ports_str}}}') if negated_ports: negated_ports_str = ','.join(negated_ports) output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}') if 'group' in side_conf: group = side_conf['group'] - if 'address_group' in group: - group_name = group['address_group'] - operator = '' - exclude = group_name[0] == "!" - if exclude: - operator = '!=' - group_name = group_name[1:] - if address_mask: - operator = '!=' if exclude else '==' - operator = f'& {address_mask} {operator}' - output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}') - elif 'dynamic_address_group' in group: + for ipvx_address_group in ['address_group', 'ipv4_address_group', 'ipv6_address_group']: + if ipvx_address_group in group: + group_name = group[ipvx_address_group] + operator = '' + exclude = group_name[0] == "!" + if exclude: + operator = '!=' + group_name = group_name[1:] + if address_mask: + operator = '!=' if exclude else '==' + operator = f'& {address_mask} {operator}' + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_address_group == 'ipv4_address_group' else 'ip6' + def_suffix = '6' if ipvx_address_group == 'ipv6_address_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}') + for ipvx_network_group in ['network_group', 'ipv4_network_group', 'ipv6_network_group']: + if ipvx_network_group in group: + group_name = group[ipvx_network_group] + operator = '' + if group_name[0] == "!": + operator = '!=' + group_name = group_name[1:] + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_network_group == 'ipv4_network_group' else 'ip6' + def_suffix = '6' if ipvx_network_group == 'ipv6_network_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}') + if 'dynamic_address_group' in group: group_name = group['dynamic_address_group'] operator = '' - exclude = group_name[0] == "!" - if exclude: + if group_name[0] == "!": operator = '!=' group_name = group_name[1:] output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}') # Generate firewall group domain-group elif 'domain_group' in group: group_name = group['domain_group'] operator = '' if group_name[0] == '!': operator = '!=' group_name = group_name[1:] output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}') - elif 'network_group' in group: - group_name = group['network_group'] - operator = '' - if group_name[0] == '!': - operator = '!=' - group_name = group_name[1:] - output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] operator = '' if group_name[0] == '!': operator = '!=' group_name = group_name[1:] output.append(f'ether {prefix}addr {operator} @M_{group_name}') if 'port_group' in group: proto = rule_conf['protocol'] group_name = group['port_group'] if proto == 'tcp_udp': proto = 'th' operator = '' if group_name[0] == '!': operator = '!=' group_name = group_name[1:] output.append(f'{proto} {prefix}port {operator} @P_{group_name}') if dict_search_args(rule_conf, 'action') == 'synproxy': output.append('ct state invalid,untracked') if 'hop_limit' in rule_conf: operators = {'eq': '==', 'gt': '>', 'lt': '<'} for op, operator in operators.items(): if op in rule_conf['hop_limit']: value = rule_conf['hop_limit'][op] output.append(f'ip6 hoplimit {operator} {value}') if 'inbound_interface' in rule_conf: operator = '' if 'name' in rule_conf['inbound_interface']: iiface = rule_conf['inbound_interface']['name'] if iiface[0] == '!': operator = '!=' iiface = iiface[1:] output.append(f'iifname {operator} {{{iiface}}}') elif 'group' in rule_conf['inbound_interface']: iiface = rule_conf['inbound_interface']['group'] if iiface[0] == '!': operator = '!=' iiface = iiface[1:] output.append(f'iifname {operator} @I_{iiface}') if 'outbound_interface' in rule_conf: operator = '' if 'name' in rule_conf['outbound_interface']: oiface = rule_conf['outbound_interface']['name'] if oiface[0] == '!': operator = '!=' oiface = oiface[1:] output.append(f'oifname {operator} {{{oiface}}}') elif 'group' in rule_conf['outbound_interface']: oiface = rule_conf['outbound_interface']['group'] if oiface[0] == '!': operator = '!=' oiface = oiface[1:] output.append(f'oifname {operator} @I_{oiface}') if 'ttl' in rule_conf: operators = {'eq': '==', 'gt': '>', 'lt': '<'} for op, operator in operators.items(): if op in rule_conf['ttl']: value = rule_conf['ttl'][op] output.append(f'ip ttl {operator} {value}') for icmp in ['icmp', 'icmpv6']: if icmp in rule_conf: if 'type_name' in rule_conf[icmp]: output.append(icmp + ' type ' + rule_conf[icmp]['type_name']) else: if 'code' in rule_conf[icmp]: output.append(icmp + ' code ' + rule_conf[icmp]['code']) if 'type' in rule_conf[icmp]: output.append(icmp + ' type ' + rule_conf[icmp]['type']) if 'packet_length' in rule_conf: lengths_str = ','.join(rule_conf['packet_length']) output.append(f'ip{def_suffix} length {{{lengths_str}}}') if 'packet_length_exclude' in rule_conf: negated_lengths_str = ','.join(rule_conf['packet_length_exclude']) output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}') if 'packet_type' in rule_conf: output.append(f'pkttype ' + rule_conf['packet_type']) if 'dscp' in rule_conf: dscp_str = ','.join(rule_conf['dscp']) output.append(f'ip{def_suffix} dscp {{{dscp_str}}}') if 'dscp_exclude' in rule_conf: negated_dscp_str = ','.join(rule_conf['dscp_exclude']) output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}') if 'ipsec' in rule_conf: if 'match_ipsec_in' in rule_conf['ipsec']: output.append('meta ipsec == 1') if 'match_none_in' in rule_conf['ipsec']: output.append('meta ipsec == 0') if 'match_ipsec_out' in rule_conf['ipsec']: output.append('rt ipsec exists') if 'match_none_out' in rule_conf['ipsec']: output.append('rt ipsec missing') if 'fragment' in rule_conf: # Checking for fragmentation after priority -400 is not possible, # so we use a priority -450 hook to set a mark if 'match_frag' in rule_conf['fragment']: output.append('meta mark 0xffff1') if 'match_non_frag' in rule_conf['fragment']: output.append('meta mark != 0xffff1') if 'limit' in rule_conf: if 'rate' in rule_conf['limit']: output.append(f'limit rate {rule_conf["limit"]["rate"]}') if 'burst' in rule_conf['limit']: output.append(f'burst {rule_conf["limit"]["burst"]} packets') if 'recent' in rule_conf: count = rule_conf['recent']['count'] time = rule_conf['recent']['time'] output.append(f'add @RECENT{def_suffix}_{hook}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}') if 'time' in rule_conf: output.append(parse_time(rule_conf['time'])) tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') if tcp_flags: output.append(parse_tcp_flags(tcp_flags)) # TCP MSS tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss') if tcp_mss: output.append(f'tcp option maxseg size {tcp_mss}') if 'connection_mark' in rule_conf: conn_mark_str = ','.join(rule_conf['connection_mark']) output.append(f'ct mark {{{conn_mark_str}}}') if 'mark' in rule_conf: mark = rule_conf['mark'] operator = '' if mark[0] == '!': operator = '!=' mark = mark[1:] output.append(f'meta mark {operator} {{{mark}}}') if 'vlan' in rule_conf: if 'id' in rule_conf['vlan']: output.append(f'vlan id {rule_conf["vlan"]["id"]}') if 'priority' in rule_conf['vlan']: output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') if 'log' in rule_conf: action = rule_conf['action'] if 'action' in rule_conf else 'accept' #output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') output.append(f'log prefix "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"') ##{family}-{hook}-{fw_name}-{rule_id} if 'log_options' in rule_conf: if 'level' in rule_conf['log_options']: log_level = rule_conf['log_options']['level'] output.append(f'log level {log_level}') if 'group' in rule_conf['log_options']: log_group = rule_conf['log_options']['group'] output.append(f'log group {log_group}') if 'queue_threshold' in rule_conf['log_options']: queue_threshold = rule_conf['log_options']['queue_threshold'] output.append(f'queue-threshold {queue_threshold}') if 'snapshot_length' in rule_conf['log_options']: log_snaplen = rule_conf['log_options']['snapshot_length'] output.append(f'snaplen {log_snaplen}') output.append('counter') if 'add_address_to_group' in rule_conf: for side in ['destination_address', 'source_address']: if side in rule_conf['add_address_to_group']: prefix = side[0] side_conf = rule_conf['add_address_to_group'][side] dyn_group = side_conf['address_group'] if 'timeout' in side_conf: timeout_value = side_conf['timeout'] output.append(f'set update ip{def_suffix} {prefix}addr timeout {timeout_value} @DA{def_suffix}_{dyn_group}') else: output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}') set_table = False if 'set' in rule_conf: # Parse set command used in policy route: if 'connection_mark' in rule_conf['set']: conn_mark = rule_conf['set']['connection_mark'] output.append(f'ct mark set {conn_mark}') if 'dscp' in rule_conf['set']: dscp = rule_conf['set']['dscp'] output.append(f'ip{def_suffix} dscp set {dscp}') if 'mark' in rule_conf['set']: mark = rule_conf['set']['mark'] output.append(f'meta mark set {mark}') if 'vrf' in rule_conf['set']: set_table = True vrf_name = rule_conf['set']['vrf'] if vrf_name == 'default': table = rt_global_vrf else: # NOTE: VRF->table ID lookup depends on the VRF iface already existing. table = get_vrf_tableid(vrf_name) if 'table' in rule_conf['set']: set_table = True table = rule_conf['set']['table'] if table == 'main': table = rt_global_table if set_table: mark = 0x7FFFFFFF - int(table) output.append(f'meta mark set {mark}') if 'tcp_mss' in rule_conf['set']: mss = rule_conf['set']['tcp_mss'] output.append(f'tcp option maxseg size set {mss}') if 'action' in rule_conf: if rule_conf['action'] == 'offload': offload_target = rule_conf['offload_target'] output.append(f'flow add @VYOS_FLOWTABLE_{offload_target}') else: output.append(f'{rule_conf["action"]}') if 'jump' in rule_conf['action']: target = rule_conf['jump_target'] output.append(f'NAME{def_suffix}_{target}') if 'queue' in rule_conf['action']: if 'queue' in rule_conf: target = rule_conf['queue'] output.append(f'num {target}') if 'queue_options' in rule_conf: queue_opts = ','.join(rule_conf['queue_options']) output.append(f'{queue_opts}') # Synproxy if 'synproxy' in rule_conf: synproxy_mss = dict_search_args(rule_conf, 'synproxy', 'tcp', 'mss') if synproxy_mss: output.append(f'mss {synproxy_mss}') synproxy_ws = dict_search_args(rule_conf, 'synproxy', 'tcp', 'window_scale') if synproxy_ws: output.append(f'wscale {synproxy_ws} timestamp sack-perm') else: if set_table: output.append('return') output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"') return " ".join(output) def parse_tcp_flags(flags): include = [flag for flag in flags if flag != 'not'] exclude = list(flags['not']) if 'not' in flags else [] return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}' def parse_time(time): out = [] if 'startdate' in time: start = time['startdate'] if 'T' not in start and 'starttime' in time: start += f' {time["starttime"]}' out.append(f'time >= "{start}"') if 'starttime' in time and 'startdate' not in time: out.append(f'hour >= "{time["starttime"]}"') if 'stopdate' in time: stop = time['stopdate'] if 'T' not in stop and 'stoptime' in time: stop += f' {time["stoptime"]}' out.append(f'time < "{stop}"') if 'stoptime' in time and 'stopdate' not in time: out.append(f'hour < "{time["stoptime"]}"') if 'weekdays' in time: days = time['weekdays'].split(",") out_days = [f'"{day}"' for day in days if day[0] != '!'] out.append(f'day {{{",".join(out_days)}}}') return " ".join(out) # GeoIP nftables_geoip_conf = '/run/nftables-geoip.conf' geoip_database = '/usr/share/vyos-geoip/dbip-country-lite.csv.gz' geoip_lock_file = '/run/vyos-geoip.lock' def geoip_load_data(codes=[]): data = None if not os.path.exists(geoip_database): return [] try: with gzip.open(geoip_database, mode='rt') as csv_fh: reader = csv.reader(csv_fh) out = [] for start, end, code in reader: if code.lower() in codes: out.append([start, end, code.lower()]) return out except: print('Error: Failed to open GeoIP database') return [] def geoip_download_data(): url = 'https://download.db-ip.com/free/dbip-country-lite-{}.csv.gz'.format(strftime("%Y-%m")) try: dirname = os.path.dirname(geoip_database) if not os.path.exists(dirname): os.mkdir(dirname) download(geoip_database, url) print("Downloaded GeoIP database") return True except: print("Error: Failed to download GeoIP database") return False class GeoIPLock(object): def __init__(self, file): self.file = file def __enter__(self): if os.path.exists(self.file): return False Path(self.file).touch() return True def __exit__(self, exc_type, exc_value, tb): os.unlink(self.file) def geoip_update(firewall, force=False): with GeoIPLock(geoip_lock_file) as lock: if not lock: print("Script is already running") return False if not firewall: print("Firewall is not configured") return True if not os.path.exists(geoip_database): if not geoip_download_data(): return False elif force: geoip_download_data() ipv4_codes = {} ipv6_codes = {} ipv4_sets = {} ipv6_sets = {} # Map country codes to set names for codes, path in dict_search_recursive(firewall, 'country_code'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' if ( path[0] == 'ipv4'): for code in codes: ipv4_codes.setdefault(code, []).append(set_name) elif ( path[0] == 'ipv6' ): set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' for code in codes: ipv6_codes.setdefault(code, []).append(set_name) if not ipv4_codes and not ipv6_codes: if force: print("GeoIP not in use by firewall") return True geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes]) # Iterate IP blocks to assign to sets for start, end, code in geoip_data: ipv4 = is_ipv4(start) if code in ipv4_codes and ipv4: ip_range = f'{start}-{end}' if start != end else start for setname in ipv4_codes[code]: ipv4_sets.setdefault(setname, []).append(ip_range) if code in ipv6_codes and not ipv4: ip_range = f'{start}-{end}' if start != end else start for setname in ipv6_codes[code]: ipv6_sets.setdefault(setname, []).append(ip_range) render(nftables_geoip_conf, 'firewall/nftables-geoip-update.j2', { 'ipv4_sets': ipv4_sets, 'ipv6_sets': ipv6_sets }) result = run(f'nft --file {nftables_geoip_conf}') if result != 0: print('Error: GeoIP failed to update firewall') return False return True diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index e6317050c..551f8ce65 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -1,1075 +1,1104 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest from glob import glob from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.process import run from vyos.utils.file import read_file sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'}, 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'default': '1', 'test_value': 'enable'}, 'directed_broadcast': {'sysfs': '/proc/sys/net/ipv4/conf/all/bc_forwarding', 'default': '1', 'test_value': 'disable'}, 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route', 'default': '0', 'test_value': 'enable'}, 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'default': '-1', 'test_value': 'enable'}, 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians', 'default': '1', 'test_value': 'disable'}, 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'}, 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects', 'default': '1', 'test_value': 'disable'}, 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies', 'default': '1', 'test_value': 'disable'}, 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'} } def get_sysctl(parameter): tmp = parameter.replace(r'.', r'/') return read_file(f'/proc/sys/{tmp}') class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestFirewall, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, ['firewall']) @classmethod def tearDownClass(cls): super(TestFirewall, cls).tearDownClass() def tearDown(self): self.cli_delete(['firewall']) self.cli_commit() # Verify chains/sets are cleaned up from nftables nftables_search = [ ['set M_smoketest_mac'], ['set N_smoketest_network'], ['set P_smoketest_port'], ['set D_smoketest_domain'], ['set RECENT_smoketest_4'], ['chain NAME_smoketest'] ] self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True) def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): # Resolver no longer blocks commit, need to wait for daemon to populate set count = 0 while count < max_wait: code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') if code == 0: return True count += 1 sleep(1) return False def test_geoip(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match']) self.cli_commit() nftables_search = [ ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'], ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept'] ] # -t prevents 1000+ GeoIP elements being returned self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t') def test_groups(self): hostmap_path = ['system', 'static-host-mapping', 'host-name'] example_org = ['192.0.2.8', '192.0.2.10', '192.0.2.11'] self.cli_set(hostmap_path + ['example.com', 'inet', '192.0.2.5']) for ips in example_org: self.cli_set(hostmap_path + ['example.org', 'inet', ips]) self.cli_commit() self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org']) self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0']) self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'group', '!smoketest_interface']) self.cli_commit() self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5') nftables_search = [ ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'], ['elements = { 172.16.99.0/24 }'], ['elements = { 53, 123 }'], ['ether saddr @M_smoketest_mac', 'accept'], ['elements = { 00:01:02:03:04:05 }'], ['set D_smoketest_domain'], ['elements = { 192.0.2.5, 192.0.2.8,'], ['192.0.2.10, 192.0.2.11 }'], ['ip saddr @D_smoketest_domain', 'accept'], ['oifname != @I_smoketest_interface', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.cli_delete(['system', 'static-host-mapping']) self.cli_commit() def test_nested_groups(self): self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_commit() # Test circular includes self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) nftables_search = [ ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'], ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'], ['elements = { 53, 123 }'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_basic_rules(self): name = 'smoketest' interface = 'eth0' interface_inv = '!eth0' interface_wc = 'l2tp*' mss_range = '501-1460' conn_mark = '555' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'name', interface_wc]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark]) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'name', interface_inv]) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark]) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'notrack']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) self.cli_commit() mark_hex = "{0:#010x}".format(int(conn_mark)) nftables_search = [ ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['tcp dport 22', 'limit rate 5/minute', 'accept'], ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'], ['log prefix "[ipv4-FWD-filter-default-D]"','FWD-filter default-action drop', 'drop'], ['chain VYOS_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'], ['meta l4proto gre', f'ct mark {mark_hex}', 'return'], ['INP-filter default-action accept', 'accept'], ['chain VYOS_OUTPUT_filter'], ['type filter hook output priority filter; policy accept;'], ['meta l4proto gre', f'oifname != "{interface}"', 'drop'], ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'], ['log prefix "[ipv4-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], ['chain VYOS_OUTPUT_raw'], ['type filter hook output priority raw; policy accept;'], ['udp', 'accept'], ['OUT-raw default-action drop', 'drop'], ['chain VYOS_PREROUTING_raw'], ['type filter hook prerouting priority raw; policy accept;'], ['tcp dport 23', 'notrack'], ['PRE-raw default-action accept', 'accept'], ['chain NAME_smoketest'], ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], ['log prefix "[ipv4-smoketest-default-D]"','smoketest default-action', 'drop'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_advanced(self): name = 'smoketest-adv' name2 = 'smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'mark', '1010']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'mark', '!98765']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15']) self.cli_commit() nftables_search = [ ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['ip saddr 198.51.100.1', 'meta mark 0x000003f2', f'jump NAME_{name}'], ['FWD-filter default-action drop', 'drop'], ['chain VYOS_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['meta mark != 0x000181cd', 'meta l4proto tcp','queue to 3'], ['meta l4proto udp','queue flags bypass,fanout to 0-15'], ['INP-filter default-action accept', 'accept'], [f'chain NAME_{name}'], ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'], [f'log prefix "[ipv4-{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_synproxy(self): tcp_mss = '1460' tcp_wscale = '7' dport = '22' self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', dport]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'mss', tcp_mss]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'window-scale', tcp_wscale]) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'synproxy']) self.cli_commit() nftables_search = [ [f'tcp dport {dport} ct state invalid,untracked', f'synproxy mss {tcp_mss} wscale {tcp_wscale} timestamp sack-perm'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_mask(self): name = 'smoketest-mask' interface = 'eth0' self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255']) self.cli_commit() nftables_search = [ [f'daddr & 0.0.255.255 == 0.0.1.2'], [f'saddr & 0.0.255.255 != 0.0.3.4'], [f'saddr & 0.0.255.255 == @A_mask_group'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_dynamic_groups(self): group01 = 'knock01' group02 = 'allowed' self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group01]) self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group02]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) self.cli_commit() nftables_search = [ [f'DA_{group01}'], [f'DA_{group02}'], ['type ipv4_addr'], ['flags dynamic,timeout'], ['chain VYOS_INPUT_filter {'], ['type filter hook input priority filter', 'policy accept'], ['tcp dport 5151', f'update @DA_{group01}', '{ ip saddr timeout 30s }', 'drop'], ['tcp dport 7272', f'ip saddr @DA_{group01}', f'update @DA_{group02}', '{ ip saddr timeout 5m }', 'drop'], ['tcp dport 22', f'ip saddr @DA_{group02}', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv6_basic_rules(self): name = 'v6-smoketest' interface = 'eth0' self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-log']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre']) self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'name', interface]) self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'action', 'notrack']) self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'protocol', 'udp']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23']) self.cli_commit() nftables_search = [ ['chain VYOS_IPV6_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], ['log prefix "[ipv6-FWD-filter-default-A]"','FWD-filter default-action accept', 'accept'], ['chain VYOS_IPV6_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'], ['INP-filter default-action accept', 'accept'], ['chain VYOS_IPV6_OUTPUT_filter'], ['type filter hook output priority filter; policy accept;'], ['meta l4proto gre', f'oifname "{interface}"', 'return'], ['log prefix "[ipv6-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'], ['chain VYOS_IPV6_OUTPUT_raw'], ['type filter hook output priority raw; policy accept;'], ['udp', 'notrack'], ['OUT-raw default-action drop', 'drop'], ['chain VYOS_IPV6_PREROUTING_raw'], ['type filter hook prerouting priority raw; policy accept;'], ['tcp dport 23', 'drop'], ['PRE-raw default-action accept', 'accept'], [f'chain NAME6_{name}'], ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'], [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop'], ['jump VYOS_STATE_POLICY6'], ['chain VYOS_STATE_POLICY6'], ['ct state established', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_advanced(self): name = 'v6-smoketest-adv' name2 = 'v6-smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'mark', '!6655-7766']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name]) self.cli_commit() nftables_search = [ ['chain VYOS_IPV6_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'], ['chain VYOS_IPV6_INPUT_filter'], ['type filter hook input priority filter; policy accept;'], ['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'], [f'chain NAME6_{name}'], ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'], [f'log prefix "[ipv6-{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_mask(self): name = 'v6-smoketest-mask' interface = 'eth0' self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_commit() nftables_search = [ ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'], ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'], ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv6_dynamic_groups(self): group01 = 'knock01' group02 = 'allowed' self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group01]) self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group02]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02]) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'protocol', 'tcp']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'destination', 'port', '22']) self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02]) self.cli_commit() nftables_search = [ [f'DA6_{group01}'], [f'DA6_{group02}'], ['type ipv6_addr'], ['flags dynamic,timeout'], ['chain VYOS_IPV6_INPUT_filter {'], ['type filter hook input priority filter', 'policy accept'], ['tcp dport 5151', f'update @DA6_{group01}', '{ ip6 saddr timeout 30s }', 'drop'], ['tcp dport 7272', f'ip6 saddr @DA6_{group01}', f'update @DA6_{group02}', '{ ip6 saddr timeout 5m }', 'drop'], ['tcp dport 22', f'ip6 saddr @DA6_{group02}', 'accept'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv4_global_state(self): self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_commit() nftables_search = [ ['jump VYOS_STATE_POLICY'], ['chain VYOS_STATE_POLICY'], ['ct state established', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') # Check conntrack is enabled from state-policy self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_ipv4_state_and_status_rules(self): name = 'smoketest-state' self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'state', 'related']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'ftp']) self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'pptp']) self.cli_commit() nftables_search = [ ['ct state { established, related }', 'accept'], ['ct state invalid', 'reject'], ['ct state new', 'ct status dnat', 'accept'], ['ct state { established, new }', 'ct status snat', 'accept'], ['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'], ['drop', f'comment "{name} default-action drop"'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') - def test_bridge_basic_rules(self): + def test_bridge_firewall(self): name = 'smoketest' interface_in = 'eth0' mac_address = '00:53:00:00:00:01' vlan_id = '12' vlan_prior = '3' + # Check bridge-nf-call-iptables default value: 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '0') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') + + self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4']) + self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) self.cli_set(['firewall', 'bridge', 'name', name, 'default-log']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address]) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'name', interface_in]) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'inbound-interface', 'name', interface_in]) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'source', 'address', '192.0.2.2']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'state', 'new']) + + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack']) + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6']) + self.cli_commit() nftables_search = [ + ['set A6_AGV6'], + ['type ipv6_addr'], + ['elements', '2001:db1::1'], ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], + ['jump VYOS_STATE_POLICY'], [f'vlan id {vlan_id}', 'accept'], [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], [f'chain NAME_{name}'], [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'], - ['accept', f'{name} default-action accept'] + ['accept', f'{name} default-action accept'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'], + ['chain VYOS_PREROUTING_filter'], + ['type filter hook prerouting priority filter; policy accept;'], + ['ip6 daddr @A6_AGV6', 'notrack'] ] self.verify_nftables(nftables_search, 'bridge vyos_filter') + ## Check bridge-nf-call-iptables is set to 1, and for ipv6 remains on default 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '1') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') def test_source_validation(self): # Strict self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict']) self.cli_commit() nftables_strict_search = [ ['fib saddr . iif oif 0', 'drop'] ] self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter') self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter') # Loose self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose']) self.cli_commit() nftables_loose_search = [ ['fib saddr oif 0', 'drop'] ] self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter') self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter') def test_sysfs(self): for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) for path in paths: with open(path, 'r') as f: self.assertEqual(f.read().strip(), conf['default'], msg=path) self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']]) self.cli_commit() for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) for path in paths: with open(path, 'r') as f: self.assertNotEqual(f.read().strip(), conf['default'], msg=path) def test_timeout_sysctl(self): timeout_config = { 'net.netfilter.nf_conntrack_icmp_timeout' :{ 'cli' : ['global-options', 'timeout', 'icmp'], 'test_value' : '180', 'default_value' : '30', }, 'net.netfilter.nf_conntrack_generic_timeout' :{ 'cli' : ['global-options', 'timeout', 'other'], 'test_value' : '1200', 'default_value' : '600', }, 'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'close-wait'], 'test_value' : '30', 'default_value' : '60', }, 'net.netfilter.nf_conntrack_tcp_timeout_close' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'close'], 'test_value' : '20', 'default_value' : '10', }, 'net.netfilter.nf_conntrack_tcp_timeout_established' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'established'], 'test_value' : '1000', 'default_value' : '432000', }, 'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'fin-wait'], 'test_value' : '240', 'default_value' : '120', }, 'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'last-ack'], 'test_value' : '300', 'default_value' : '30', }, 'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'syn-recv'], 'test_value' : '100', 'default_value' : '60', }, 'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'syn-sent'], 'test_value' : '300', 'default_value' : '120', }, 'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{ 'cli' : ['global-options', 'timeout', 'tcp', 'time-wait'], 'test_value' : '303', 'default_value' : '120', }, 'net.netfilter.nf_conntrack_udp_timeout' :{ 'cli' : ['global-options', 'timeout', 'udp', 'other'], 'test_value' : '90', 'default_value' : '30', }, 'net.netfilter.nf_conntrack_udp_timeout_stream' :{ 'cli' : ['global-options', 'timeout', 'udp', 'stream'], 'test_value' : '200', 'default_value' : '180', }, } for parameter, parameter_config in timeout_config.items(): self.cli_set(['firewall'] + parameter_config['cli'] + [parameter_config['test_value']]) # commit changes self.cli_commit() # validate configuration for parameter, parameter_config in timeout_config.items(): tmp = parameter_config['test_value'] self.assertEqual(get_sysctl(f'{parameter}'), tmp) # delete all configuration options and revert back to defaults self.cli_delete(['firewall', 'global-options', 'timeout']) self.cli_commit() # validate configuration for parameter, parameter_config in timeout_config.items(): self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value']) ### Zone def test_zone_basic(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6']) self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'log']) self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept']) self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop']) self.cli_commit() nftables_search = [ ['chain VYOS_ZONE_FORWARD'], ['type filter hook forward priority filter + 1'], ['chain VYOS_ZONE_OUTPUT'], ['type filter hook output priority filter + 1'], ['chain VYOS_ZONE_LOCAL'], ['type filter hook input priority filter + 1'], ['chain VZONE_smoketest-eth0'], ['chain VZONE_smoketest-local_IN'], ['chain VZONE_smoketest-local_OUT'], ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], ['iifname "eth0"', 'jump NAME_smoketest'], ['oifname "eth0"', 'jump NAME_smoketest'], ['jump VYOS_STATE_POLICY'], ['chain VYOS_STATE_POLICY'], ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] nftables_search_v6 = [ ['chain VYOS_ZONE_FORWARD'], ['type filter hook forward priority filter + 1'], ['chain VYOS_ZONE_OUTPUT'], ['type filter hook output priority filter + 1'], ['chain VYOS_ZONE_LOCAL'], ['type filter hook input priority filter + 1'], ['chain VZONE_smoketest-eth0'], ['chain VZONE_smoketest-local_IN'], ['chain VZONE_smoketest-local_OUT'], ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], ['jump VZONE_smoketest-local_IN'], ['jump VZONE_smoketest-local_OUT'], ['iifname "eth0"', 'jump NAME6_smoketestv6'], ['jump VYOS_STATE_POLICY6'], ['chain VYOS_STATE_POLICY6'], ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'], ['ct state invalid', 'drop'], ['ct state related', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') def test_flow_offload(self): self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) # QEMU virtual NIC does not support hw-tc-offload with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established']) self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related']) self.cli_commit() nftables_search = [ ['flowtable VYOS_FLOWTABLE_smoketest'], ['hook ingress priority filter'], ['devices = { eth0.10 }'], ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'], ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search, 'ip6 vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_zone_flow_offload(self): self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) # QEMU virtual NIC does not support hw-tc-offload with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) self.cli_commit() nftables_search = [ ['chain NAME_smoketest'], ['flow add @VYOS_FLOWTABLE_smoketest'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') nftables_search = [ ['chain NAME6_smoketest'], ['flow add @VYOS_FLOWTABLE_smoketest'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') # Check conntrack self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') def test_ipsec_metadata_match(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'ipsec', 'match-ipsec-in']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'action', 'drop']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'ipsec', 'match-none-in']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'action', 'continue']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'ipsec', 'match-ipsec-out']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'ipsec', 'match-none-out']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'ipsec', 'match-ipsec-in']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'ipsec', 'match-none-in']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'action', 'continue']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'ipsec', 'match-ipsec-out']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'ipsec', 'match-none-out']) self.cli_commit() nftables_search = [ ['meta ipsec exists', 'accept comment'], ['meta ipsec missing', 'drop comment'], ['rt ipsec exists', 'continue comment'], ['rt ipsec missing', 'reject comment'], ] self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search, 'ip6 vyos_filter') self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4']) # All valid directional usage of ipsec matches self.cli_commit() self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in-indirect']) # nft does not support ANY usage of 'meta ipsec' under an output hook, it will fail to load cfg with self.assertRaises(ConfigSessionError): self.cli_commit() def test_cyclic_jump_validation(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'jump-target', 'smoketest-cycle-2']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'jump-target', 'smoketest-cycle-3']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'log']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-cycle-1']) # Multi-level jumps are unwise but allowed self.cli_commit() self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'jump']) self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'jump-target', 'smoketest-cycle-1']) # nft will fail to load cyclic jumps in any form, whether the rule is reachable or not. # It should be caught by conf validation. with self.assertRaises(ConfigSessionError): self.cli_commit() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 352d5cbb1..02bf00bcc 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -1,532 +1,567 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os import re from sys import exit from vyos.base import Warning from vyos.config import Config from vyos.configdict import is_node_changed from vyos.configdiff import get_config_diff, Diff from vyos.configdep import set_dependents, call_dependents from vyos.configverify import verify_interface_exists from vyos.ethtool import Ethtool from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update from vyos.template import render from vyos.utils.dict import dict_search_args from vyos.utils.dict import dict_search_recursive from vyos.utils.process import call from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag +from subprocess import run as subp_run airbag.enable() nftables_conf = '/run/nftables.conf' sysctl_file = r'/run/sysctl/10-vyos-firewall.conf' valid_groups = [ 'address_group', 'domain_group', 'network_group', 'port_group', - 'interface_group' + 'interface_group', + ## Added for group ussage in bridge firewall + 'ipv4_address_group', + 'ipv6_address_group', + 'ipv4_network_group', + 'ipv6_network_group' ] nested_group_types = [ 'address_group', 'network_group', 'mac_group', 'port_group', 'ipv6_address_group', 'ipv6_network_group' ] snmp_change_type = { 'unknown': 0, 'add': 1, 'delete': 2, 'change': 3 } snmp_event_source = 1 snmp_trap_mib = 'VYATTA-TRAP-MIB' snmp_trap_name = 'mgmtEventTrap' def geoip_updated(conf, firewall): diff = get_config_diff(conf) node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True) out = { 'name': [], 'ipv6_name': [], 'deleted_name': [], 'deleted_ipv6_name': [] } updated = False for key, path in dict_search_recursive(firewall, 'geoip'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' if (path[0] == 'ipv4'): out['name'].append(set_name) elif (path[0] == 'ipv6'): set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' out['ipv6_name'].append(set_name) updated = True if 'delete' in node_diff: for key, path in dict_search_recursive(node_diff['delete'], 'geoip'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' if (path[0] == 'ipv4'): out['deleted_name'].append(set_name) elif (path[0] == 'ipv6'): set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' out['deleted_ipv6_name'].append(set_name) updated = True if updated: return out return False def get_config(config=None): if config: conf = config else: conf = Config() base = ['firewall'] firewall = conf.get_config_dict(base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True, with_recursive_defaults=True) firewall['group_resync'] = bool('group' in firewall or is_node_changed(conf, base + ['group'])) if firewall['group_resync']: # Update nat and policy-route as firewall groups were updated set_dependents('group_resync', conf) firewall['geoip_updated'] = geoip_updated(conf, firewall) fqdn_config_parse(firewall) set_dependents('conntrack', conf) return firewall -def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False): +def verify_jump_target(firewall, hook, jump_target, family, recursive=False): targets_seen = [] targets_pending = [jump_target] while targets_pending: target = targets_pending.pop() - if not ipv6: - if target not in dict_search_args(firewall, 'ipv4', 'name'): - raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') - target_rules = dict_search_args(firewall, 'ipv4', 'name', target, 'rule') - else: - if target not in dict_search_args(firewall, 'ipv6', 'name'): - raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') - target_rules = dict_search_args(firewall, 'ipv6', 'name', target, 'rule') + if 'name' not in firewall[family]: + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') + elif target not in dict_search_args(firewall, family, 'name'): + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') - no_ipsec_in = root_chain in ('output', ) + target_rules = dict_search_args(firewall, family, 'name', target, 'rule') + no_ipsec_in = hook in ('output', ) if target_rules: for target_rule_conf in target_rules.values(): # Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets: if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \ or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None): - if not ipv6: - raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall name {target} rules contain incompatible ipsec inbound matches') - else: - raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall ipv6 name {target} rules contain incompatible ipsec inbound matches') + raise ConfigError(f'Invalid jump-target for {hook}. Firewall {family} name {target} rules contain incompatible ipsec inbound matches') # Make sure we're not looping back on ourselves somewhere: if recursive and 'jump_target' in target_rule_conf: child_target = target_rule_conf['jump_target'] if child_target in targets_seen: - if not ipv6: - raise ConfigError(f'Loop detected in jump-targets, firewall name {target} refers to previously traversed name {child_target}') - else: - raise ConfigError(f'Loop detected in jump-targets, firewall ipv6 name {target} refers to previously traversed ipv6 name {child_target}') + raise ConfigError(f'Loop detected in jump-targets, firewall {family} name {target} refers to previously traversed {family} name {child_target}') targets_pending.append(child_target) if len(targets_seen) == 7: path_txt = ' -> '.join(targets_seen) Warning(f'Deep nesting of jump targets has reached 8 levels deep, following the path {path_txt} -> {child_target}!') targets_seen.append(target) -def verify_rule(firewall, chain_name, rule_conf, ipv6): +def verify_rule(firewall, family, hook, priority, rule_id, rule_conf): if 'action' not in rule_conf: raise ConfigError('Rule action must be defined') if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf: raise ConfigError('Action set to jump, but no jump-target specified') if 'jump_target' in rule_conf: if 'jump' not in rule_conf['action']: raise ConfigError('jump-target defined, but action jump needed and it is not defined') target = rule_conf['jump_target'] - if chain_name != 'name': # This is a bit clumsy, but consolidates a chunk of code. - verify_jump_target(firewall, chain_name, target, ipv6, recursive=True) + if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code. + verify_jump_target(firewall, hook, target, family, recursive=True) else: - verify_jump_target(firewall, chain_name, target, ipv6, recursive=False) + verify_jump_target(firewall, hook, target, family, recursive=False) if rule_conf['action'] == 'offload': if 'offload_target' not in rule_conf: raise ConfigError('Action set to offload, but no offload-target specified') offload_target = rule_conf['offload_target'] if not dict_search_args(firewall, 'flowtable', offload_target): raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system') if rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf: raise ConfigError('"synproxy" option allowed only for action synproxy') if rule_conf['action'] == 'synproxy': if 'state' in rule_conf: raise ConfigError('For action "synproxy" state cannot be defined') if not rule_conf.get('synproxy', {}).get('tcp'): raise ConfigError('synproxy TCP MSS is not defined') if rule_conf.get('protocol', {}) != 'tcp': raise ConfigError('For action "synproxy" the protocol must be set to TCP') if 'queue_options' in rule_conf: if 'queue' not in rule_conf['action']: raise ConfigError('queue-options defined, but action queue needed and it is not defined') if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']): raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range') if 'queue' in rule_conf and 'queue' not in rule_conf['action']: raise ConfigError('queue defined, but action queue needed and it is not defined') if 'fragment' in rule_conf: if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') if 'limit' in rule_conf: if 'rate' in rule_conf['limit']: rate_int = re.sub(r'\D', '', rule_conf['limit']['rate']) if int(rate_int) < 1: raise ConfigError('Limit rate integer cannot be less than 1') if 'ipsec' in rule_conf: if {'match_ipsec_in', 'match_none_in'} <= set(rule_conf['ipsec']): raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') if {'match_ipsec_out', 'match_none_out'} <= set(rule_conf['ipsec']): raise ConfigError('Cannot specify both "match-ipsec" and "match-none"') if 'recent' in rule_conf: if not {'count', 'time'} <= set(rule_conf['recent']): raise ConfigError('Recent "count" and "time" values must be defined') tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') if tcp_flags: if dict_search_args(rule_conf, 'protocol') != 'tcp': raise ConfigError('Protocol must be tcp when specifying tcp flags') not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not') if not_flags: duplicates = [flag for flag in tcp_flags if flag in not_flags] if duplicates: raise ConfigError(f'Cannot match a tcp flag as set and not set') if 'protocol' in rule_conf: - if rule_conf['protocol'] == 'icmp' and ipv6: + if rule_conf['protocol'] == 'icmp' and family == 'ipv6': raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp') - if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6: + if rule_conf['protocol'] == 'ipv6-icmp' and family == 'ipv4': raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp') for side in ['destination', 'source']: if side in rule_conf: side_conf = rule_conf[side] if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1: raise ConfigError('Only one of address, fqdn or geoip can be specified') if 'group' in side_conf: if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1: raise ConfigError('Only one address-group, network-group or domain-group can be specified') for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] - fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + if family == 'ipv6' and group in ['address_group', 'network_group']: + fw_group = f'ipv6_{group}' + elif family == 'bridge': + if group =='ipv4_address_group': + fw_group = 'address_group' + elif group == 'ipv4_network_group': + fw_group = 'network_group' + else: + fw_group = group + else: + fw_group = group + error_group = fw_group.replace("_", "-") if group in ['address_group', 'network_group', 'domain_group']: types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf] if types: raise ConfigError(f'{error_group} and {types[0]} cannot both be defined') if group_name and group_name[0] == '!': group_name = group_name[1:] group_obj = dict_search_args(firewall, 'group', fw_group, group_name) if group_obj is None: raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') if not group_obj: Warning(f'{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') if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'): raise ConfigError(f'{side} port-group and port cannot both be defined') if 'add_address_to_group' in rule_conf: for type in ['destination_address', 'source_address']: if type in rule_conf['add_address_to_group']: if 'address_group' not in rule_conf['add_address_to_group'][type]: raise ConfigError(f'Dynamic address group must be defined.') else: target = rule_conf['add_address_to_group'][type]['address_group'] - fwall_group = 'ipv6_address_group' if ipv6 else 'address_group' + fwall_group = 'ipv6_address_group' if family == 'ipv6' else 'address_group' group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target) if group_obj is None: raise ConfigError(f'Invalid dynamic address group on firewall rule') if 'log_options' in rule_conf: if 'log' not in rule_conf: raise ConfigError('log-options defined, but log is not enable') if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: raise ConfigError('log-options snapshot-length defined, but log group is not define') if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: raise ConfigError('log-options queue-threshold defined, but log group is not define') for direction in ['inbound_interface','outbound_interface']: if direction in rule_conf: if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]: raise ConfigError(f'Cannot specify both interface group and interface name for {direction}') if 'group' in rule_conf[direction]: group_name = rule_conf[direction]['group'] if group_name[0] == '!': group_name = group_name[1:] group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name) if group_obj is None: raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule') if not group_obj: Warning(f'interface-group "{group_name}" has no members!') def verify_nested_group(group_name, group, groups, seen): if 'include' not in group: return seen.append(group_name) for g in group['include']: if g not in groups: raise ConfigError(f'Nested group "{g}" does not exist') if g in seen: raise ConfigError(f'Group "{group_name}" has a circular reference') if 'include' in groups[g]: verify_nested_group(g, groups[g], groups, seen) def verify_hardware_offload(ifname): ethtool = Ethtool(ifname) enabled, fixed = ethtool.get_hw_tc_offload() if not enabled and fixed: raise ConfigError(f'Interface "{ifname}" does not support hardware offload') if not enabled: raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"') def verify(firewall): if 'flowtable' in firewall: for flowtable, flowtable_conf in firewall['flowtable'].items(): if 'interface' not in flowtable_conf: raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface') for ifname in flowtable_conf['interface']: verify_interface_exists(ifname) if dict_search_args(flowtable_conf, 'offload') == 'hardware': interfaces = flowtable_conf['interface'] for ifname in interfaces: verify_hardware_offload(ifname) if 'group' in firewall: for group_type in nested_group_types: if group_type in firewall['group']: groups = firewall['group'][group_type] for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) - if 'ipv4' in firewall: - for name in ['name','forward','input','output', 'prerouting']: - if name in firewall['ipv4']: - for name_id, name_conf in firewall['ipv4'][name].items(): - if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: - raise ConfigError('default-action set to jump, but no default-jump-target specified') - if 'default_jump_target' in name_conf: - target = name_conf['default_jump_target'] - if 'jump' not in name_conf['default_action']: - raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') - if name_conf['default_jump_target'] == name_id: - raise ConfigError(f'Loop detected on default-jump-target.') - verify_jump_target(firewall, name, target, False, recursive=True) - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, name, rule_conf, False) - - if 'ipv6' in firewall: - for name in ['name','forward','input','output', 'prerouting']: - if name in firewall['ipv6']: - for name_id, name_conf in firewall['ipv6'][name].items(): - if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: - raise ConfigError('default-action set to jump, but no default-jump-target specified') - if 'default_jump_target' in name_conf: - target = name_conf['default_jump_target'] - if 'jump' not in name_conf['default_action']: - raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') - if name_conf['default_jump_target'] == name_id: - raise ConfigError(f'Loop detected on default-jump-target.') - verify_jump_target(firewall, name, target, True, recursive=True) - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, name, rule_conf, True) - - #### ZONESSSS + for family in ['ipv4', 'ipv6', 'bridge']: + if family in firewall: + for chain in ['name','forward','input','output', 'prerouting']: + if chain in firewall[family]: + for priority, priority_conf in firewall[family][chain].items(): + if 'jump' in priority_conf['default_action'] and 'default_jump_target' not in priority_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in priority_conf: + target = priority_conf['default_jump_target'] + if 'jump' not in priority_conf['default_action']: + raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') + if priority_conf['default_jump_target'] == priority: + raise ConfigError(f'Loop detected on default-jump-target.') + if target not in dict_search_args(firewall[family], 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + if 'rule' in priority_conf: + for rule_id, rule_conf in priority_conf['rule'].items(): + verify_rule(firewall, family, chain, priority, rule_id, rule_conf) + local_zone = False zone_interfaces = [] if 'zone' in firewall: for zone, zone_conf in firewall['zone'].items(): if 'local_zone' not in zone_conf and 'interface' not in zone_conf: raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') if 'local_zone' in zone_conf: if local_zone: raise ConfigError('There cannot be multiple local zones') if 'interface' in zone_conf: raise ConfigError('Local zone cannot have interfaces assigned') if 'intra_zone_filtering' in zone_conf: raise ConfigError('Local zone cannot use intra-zone-filtering') local_zone = True if 'interface' in zone_conf: found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] if found_duplicates: raise ConfigError(f'Interfaces cannot be assigned to multiple zones') zone_interfaces += zone_conf['interface'] if 'intra_zone_filtering' in zone_conf: intra_zone = zone_conf['intra_zone_filtering'] if len(intra_zone) > 1: raise ConfigError('Only one intra-zone-filtering action must be specified') if 'firewall' in intra_zone: v4_name = dict_search_args(intra_zone, 'firewall', 'name') if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): raise ConfigError(f'Firewall name "{v4_name}" does not exist') v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') if not v4_name and not v6_name: raise ConfigError('No firewall names specified for intra-zone-filtering') if 'from' in zone_conf: for from_zone, from_conf in zone_conf['from'].items(): if from_zone not in firewall['zone']: raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') v4_name = dict_search_args(from_conf, 'firewall', 'name') if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): raise ConfigError(f'Firewall name "{v4_name}" does not exist') v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') return None def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True if 'zone' in firewall: for local_zone, local_zone_conf in firewall['zone'].items(): if 'local_zone' not in local_zone_conf: continue local_zone_conf['from_local'] = {} for zone, zone_conf in firewall['zone'].items(): if zone == local_zone or 'from' not in zone_conf: continue if local_zone in zone_conf['from']: local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] render(nftables_conf, 'firewall/nftables.j2', firewall) render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) return None +def parse_firewall_error(output): + # Define the regex patterns to extract the error message and the comment + error_pattern = re.compile(r'Error:\s*(.*?)\n') + comment_pattern = re.compile(r'comment\s+"([^"]+)"') + error_output = [] + + # Find all error messages in the output + error_matches = error_pattern.findall(output) + # Find all comment matches in the output + comment_matches = comment_pattern.findall(output) + + if not error_matches or not comment_matches: + raise ConfigError(f'Unknown firewall error detected: {output}') + + error_output.append('Fail to apply firewall') + # Loop over the matches and process them + for error_message, comment in zip(error_matches, comment_matches): + # Parse the comment + parsed_entries = comment.split('-') + family = 'bridge' if parsed_entries[0] == 'bri' else parsed_entries[0] + if parsed_entries[1] == 'NAM': + chain = 'name' + elif parsed_entries[1] == 'FWD': + chain = 'forward' + elif parsed_entries[1] == 'INP': + chain = 'input' + elif parsed_entries[1] == 'OUT': + chain = 'output' + elif parsed_entries[1] == 'PRE': + chain = 'prerouting' + error_output.append(f'Error found on: firewall {family} {chain} {parsed_entries[2]} rule {parsed_entries[3]}') + error_output.append(f'\tError message: {error_message.strip()}') + + raise ConfigError('\n'.join(error_output)) + def apply(firewall): + # Use nft -c option to check current configuration file + completed_process = subp_run(['nft', '-c', '--file', nftables_conf], capture_output=True) + install_result = completed_process.returncode + if install_result == 1: + # We need to handle firewall error + output = completed_process.stderr + parse_firewall_error(output.decode()) + + # No error detected during check, we can apply the new configuration install_result, output = rc_cmd(f'nft --file {nftables_conf}') + # Double check just in case if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') # Apply firewall global-options sysctl settings cmd(f'sysctl -f {sysctl_file}') call_dependents() # T970 Enable a resolver (systemd daemon) that checks # domain-group/fqdn addresses and update entries for domains by timeout # If router loaded without internet connection or for synchronization domain_action = 'stop' if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']: domain_action = 'restart' call(f'systemctl {domain_action} vyos-domain-resolver.service') if firewall['geoip_updated']: # Call helper script to Update set contents if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']: print('Updating GeoIP. Please wait...') geoip_update(firewall) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1) diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index c9b8ef8fe..76be41ddc 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -1,112 +1,117 @@ # # VyOS specific sysctl settings, see sysctl.conf (5) for information. # # Panic on OOPS kernel.panic_on_oops=1 # Timeout before rebooting on panic kernel.panic=60 # Send all core files to /var/core/core.program.pid.time kernel.core_pattern=/var/core/core-%e-%p-%t # ARP configuration # arp_filter - allow multiple network interfaces on same subnet # arp_announce - avoid local addresses no on target's subnet # arp_ignore - reply only if target IP is local_address on the interface # arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it. net.ipv4.conf.all.arp_filter=0 # https://vyos.dev/T300 net.ipv4.conf.all.arp_ignore=0 net.ipv4.conf.all.arp_announce=2 # Enable packet forwarding for IPv4 net.ipv4.ip_forward=1 # Enable directed broadcast forwarding feature described in rfc1812#section-5.3.5.2 and rfc2644. # Note that setting the 'all' entry to 1 doesn't enable directed broadcast forwarding on all interfaces. # To enable directed broadcast forwarding on an interface, both the 'all' entry and the input interface entry should be set to 1. net.ipv4.conf.all.bc_forwarding=1 net.ipv4.conf.default.bc_forwarding=0 # if a primary address is removed from an interface promote the # secondary address if available net.ipv4.conf.all.promote_secondaries=1 # Ignore ICMP broadcasts sent to broadcast/multicast net.ipv4.icmp_echo_ignore_broadcasts=1 # Ignore bogus ICMP errors net.ipv4.icmp_ignore_bogus_error_responses=1 # Send ICMP responses with primary address of exiting interface net.ipv4.icmp_errors_use_inbound_ifaddr=1 # Log packets with impossible addresses to kernel log net.ipv4.conf.all.log_martians=1 # Do not ignore all ICMP ECHO requests by default net.ipv4.icmp_echo_ignore_all=0 # Disable source validation by default net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0 # Enable tcp syn-cookies by default net.ipv4.tcp_syncookies=1 # Disable accept_redirects by default for any interface net.ipv4.conf.all.accept_redirects=0 net.ipv4.conf.default.accept_redirects=0 net.ipv6.conf.all.accept_redirects=0 net.ipv6.conf.default.accept_redirects=0 # Disable accept_source_route by default net.ipv4.conf.all.accept_source_route=0 net.ipv4.conf.default.accept_source_route=0 net.ipv6.conf.all.accept_source_route=0 net.ipv6.conf.default.accept_source_route=0 # Enable send_redirects by default net.ipv4.conf.all.send_redirects=1 net.ipv4.conf.default.send_redirects=1 # Increase size of buffer for netlink net.core.rmem_max=2097152 # Remove IPv4 and IPv6 routes from forward information base when link goes down net.ipv4.conf.all.ignore_routes_with_linkdown=1 net.ipv4.conf.default.ignore_routes_with_linkdown=1 net.ipv6.conf.all.ignore_routes_with_linkdown=1 net.ipv6.conf.default.ignore_routes_with_linkdown=1 # Enable packet forwarding for IPv6 net.ipv6.conf.all.forwarding=1 # Increase route table limit net.ipv6.route.max_size = 262144 # Do not forget IPv6 addresses when a link goes down net.ipv6.conf.default.keep_addr_on_down=1 net.ipv6.conf.all.keep_addr_on_down=1 net.ipv6.route.skip_notify_on_dev_down=1 # Default value of 20 seems to interfere with larger OSPF and VRRP setups net.ipv4.igmp_max_memberships = 512 # Enable global RFS (Receive Flow Steering) configuration. RFS is inactive # until explicitly configured at the interface level net.core.rps_sock_flow_entries = 32768 # Congestion control net.core.default_qdisc=fq_codel net.ipv4.tcp_congestion_control=bbr # Disable IPv6 Segment Routing packets by default net.ipv6.conf.all.seg6_enabled = 0 net.ipv6.conf.default.seg6_enabled = 0 net.vrf.strict_mode = 1 + +# https://vyos.dev/T6570 +# By default, do not forward traffic from bridge to IPvX layer +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0 \ No newline at end of file