diff options
| -rw-r--r-- | data/templates/firewall/nftables.tmpl | 22 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/common-rule.xml.i | 19 | ||||
| -rw-r--r-- | python/vyos/firewall.py | 4 | ||||
| -rwxr-xr-x | src/conf_mode/firewall.py | 6 | ||||
| -rwxr-xr-x | src/migration-scripts/firewall/6-to-7 | 20 | 
5 files changed, 63 insertions, 8 deletions
diff --git a/data/templates/firewall/nftables.tmpl b/data/templates/firewall/nftables.tmpl index 468a5a32f..0cc977cf9 100644 --- a/data/templates/firewall/nftables.tmpl +++ b/data/templates/firewall/nftables.tmpl @@ -31,16 +31,27 @@ table ip filter {      }  {% endif %}  {% if name is defined %} +{%   set ns = namespace(sets=[]) %}  {%   for name_text, conf in name.items() %}      chain NAME_{{ name_text }} {  {%     if conf.rule is defined %}  {%       for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}          {{ rule_conf | nft_rule(name_text, rule_id) }} +{%         if rule_conf.recent is defined %} +{%           set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{%         endif %}  {%       endfor %}  {%     endif %}          {{ conf | nft_default_rule(name_text) }}      }  {%   endfor %} +{%   for set_name in ns.sets %} +    set RECENT_{{ set_name }} { +        type ipv4_addr +        size 65535 +        flags dynamic +    } +{%   endfor %}  {% endif %}  {% if state_policy is defined %}      chain VYOS_STATE_POLICY { @@ -81,16 +92,27 @@ table ip6 filter {      }  {% endif %}  {% if ipv6_name is defined %} +{%   set ns = namespace(sets=[]) %}  {%   for name_text, conf in ipv6_name.items() %}      chain NAME6_{{ name_text }} {  {%     if conf.rule is defined %}  {%       for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not defined %}          {{ rule_conf | nft_rule(name_text, rule_id, 'ip6') }} +{%         if rule_conf.recent is defined %} +{%           set ns.sets = ns.sets + [name_text + '_' + rule_id] %} +{%         endif %}  {%       endfor %}  {%     endif %}          {{ conf | nft_default_rule(name_text) }}      }  {%   endfor %} +{%   for set_name in ns.sets %} +    set RECENT6_{{ set_name }} { +        type ipv6_addr +        size 65535 +        flags dynamic +    } +{%   endfor %}  {% endif %}  {% if state_policy is defined %}      chain VYOS_STATE_POLICY6 { diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 521fe54f2..353804990 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -146,13 +146,24 @@      </leafNode>      <leafNode name="time">        <properties> -        <help>Source addresses seen in the last N seconds</help> +        <help>Source addresses seen in the last second/minute/hour</help> +        <completionHelp> +          <list>second minute hour</list> +        </completionHelp>          <valueHelp> -          <format>u32:0-4294967295</format> -          <description>Source addresses seen in the last N seconds</description> +          <format>second</format> +          <description>Source addresses seen COUNT times in the last second</description> +        </valueHelp> +        <valueHelp> +          <format>minute</format> +          <description>Source addresses seen COUNT times in the last minute</description> +        </valueHelp> +        <valueHelp> +          <format>hour</format> +          <description>Source addresses seen COUNT times in the last hour</description>          </valueHelp>          <constraint> -          <validator name="numeric" argument="--range 0-4294967295"/> +          <regex>^(second|minute|hour)$</regex>          </constraint>        </properties>      </leafNode> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index c1217b420..55ce318e7 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -181,9 +181,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):      if 'recent' in rule_conf:          count = rule_conf['recent']['count']          time = rule_conf['recent']['time'] -        # output.append(f'meter {fw_name}_{rule_id} {{ ip saddr and 255.255.255.255 limit rate over {count}/{time} burst {count} packets }}') -        # Waiting on input from nftables developers due to -        # bug with above line and atomic chain flushing. +        output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')      if 'time' in rule_conf:          output.append(parse_time(rule_conf['time'])) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 9dec2143e..41df1b84a 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -278,6 +278,7 @@ def cleanup_rule(table, jump_chain):  def cleanup_commands(firewall):      commands = [] +    commands_end = []      for table in ['ip filter', 'ip6 filter']:          state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'          json_str = cmd(f'nft -j list table {table}') @@ -308,7 +309,10 @@ def cleanup_commands(firewall):                              chain = rule['chain']                              handle = rule['handle']                              commands.append(f'delete rule {table} {chain} handle {handle}') -    return commands +            elif 'set' in item: +                set_name = item['set']['name'] +                commands_end.append(f'delete set {table} {set_name}') +    return commands + commands_end  def generate(firewall):      if not os.path.exists(nftables_conf): diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index efc901530..5f4cff90d 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -104,6 +104,7 @@ if config.exists(base + ['name']):              continue          for rule in config.list_nodes(base + ['name', name, 'rule']): +            rule_recent = base + ['name', name, 'rule', rule, 'recent']              rule_time = base + ['name', name, 'rule', rule, 'time']              rule_tcp_flags = base + ['name', name, 'rule', rule, 'tcp', 'flags']              rule_icmp = base + ['name', name, 'rule', rule, 'icmp'] @@ -114,6 +115,15 @@ if config.exists(base + ['name']):              if config.exists(rule_time + ['utc']):                  config.delete(rule_time + ['utc']) +            if config.exists(rule_recent + ['time']): +                tmp = int(config.return_value(rule_recent + ['time'])) +                unit = 'minute' +                if tmp > 600: +                    unit = 'hour' +                elif tmp < 10: +                    unit = 'second' +                config.set(rule_recent + ['time'], value=unit) +              if config.exists(rule_tcp_flags):                  tmp = config.return_value(rule_tcp_flags)                  config.delete(rule_tcp_flags) @@ -148,6 +158,7 @@ if config.exists(base + ['ipv6-name']):              continue          for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): +            rule_recent = base + ['ipv6-name', name, 'rule', rule, 'recent']              rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']              rule_tcp_flags = base + ['ipv6-name', name, 'rule', rule, 'tcp', 'flags']              rule_icmp = base + ['ipv6-name', name, 'rule', rule, 'icmpv6'] @@ -158,6 +169,15 @@ if config.exists(base + ['ipv6-name']):              if config.exists(rule_time + ['utc']):                  config.delete(rule_time + ['utc']) +            if config.exists(rule_recent + ['time']): +                tmp = int(config.return_value(rule_recent + ['time'])) +                unit = 'minute' +                if tmp > 600: +                    unit = 'hour' +                elif tmp < 10: +                    unit = 'second' +                config.set(rule_recent + ['time'], value=unit) +              if config.exists(rule_tcp_flags):                  tmp = config.return_value(rule_tcp_flags)                  config.delete(rule_tcp_flags)  | 
