summaryrefslogtreecommitdiff
path: root/python/vyos/firewall.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/firewall.py')
-rw-r--r--python/vyos/firewall.py258
1 files changed, 170 insertions, 88 deletions
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 4075e55b0..3305eb269 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
+# 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
@@ -20,76 +20,50 @@ 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.util import call
-from vyos.util import cmd
-from vyos.util import dict_search_args
-from vyos.util import dict_search_recursive
-from vyos.util import run
-
-
-# Functions for firewall group domain-groups
-def get_ips_domains_dict(list_domains):
- """
- Get list of IPv4 addresses by list of domains
- Ex: get_ips_domains_dict(['ex1.com', 'ex2.com'])
- {'ex1.com': ['192.0.2.1'], 'ex2.com': ['192.0.2.2', '192.0.2.3']}
- """
- from socket import gethostbyname_ex
- from socket import gaierror
-
- ip_dict = {}
- for domain in list_domains:
- try:
- _, _, ips = gethostbyname_ex(domain)
- ip_dict[domain] = ips
- except gaierror:
- pass
-
- return ip_dict
-
-def nft_init_set(group_name, table="vyos_filter", family="ip"):
- """
- table ip vyos_filter {
- set GROUP_NAME
- type ipv4_addr
- flags interval
- }
- """
- return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}')
-
-
-def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"):
- """
- table ip vyos_filter {
- set GROUP_NAME {
- type ipv4_addr
- flags interval
- elements = { 192.0.2.1, 192.0.2.2 }
- }
- """
- elements = ", ".join(elements)
- return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ')
-
-def nft_flush_set(group_name, table="vyos_filter", family="ip"):
- """
- Flush elements of nft set
- """
- return call(f'nft flush set {family} {table} {group_name}')
-
-def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"):
- """
- Update elements of nft set
- """
- flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip")
- nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip")
- return flush_set, nft_add_set
-
-# END firewall group domain-group (sets)
+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 run
+
+# 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
@@ -111,9 +85,16 @@ def nft_action(vyos_action):
return 'return'
return vyos_action
-def parse_rule(rule_conf, fw_name, rule_id, ip_name):
+def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output = []
- def_suffix = '6' if ip_name == 'ip6' else ''
+ #def_suffix = '6' if ip_name == 'ip6' else ''
+
+ 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, v in rule_conf['state'].items() if v == 'enable'])
@@ -144,18 +125,50 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
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']
- if suffix[0] == '!':
- suffix = f'!= {suffix[1:]}'
- output.append(f'{ip_name} {prefix}addr {suffix}')
+ 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 '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 == '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 = '!='
- output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC_{fw_name}_{rule_id}')
+ if hook == 'FWD':
+ hook_name = 'forward'
+ if hook == 'INP':
+ hook_name = 'input'
+ if hook == 'OUT':
+ hook_name = 'output'
+ 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"]
@@ -192,9 +205,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'address_group' in group:
group_name = group['address_group']
operator = ''
- if group_name[0] == '!':
+ 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}')
# Generate firewall group domain-group
elif 'domain_group' in group:
@@ -234,12 +251,26 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'log' in rule_conf and rule_conf['log'] == 'enable':
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 "[{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 'log_level' in rule_conf:
- log_level = rule_conf['log_level']
- output.append(f'level {log_level}')
+ 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}')
if 'hop_limit' in rule_conf:
operators = {'eq': '==', 'gt': '>', 'lt': '<'}
@@ -249,12 +280,34 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
output.append(f'ip6 hoplimit {operator} {value}')
if 'inbound_interface' in rule_conf:
- iiface = rule_conf['inbound_interface']
- output.append(f'iifname {iiface}')
+ operator = ''
+ if 'interface_name' in rule_conf['inbound_interface']:
+ iiface = rule_conf['inbound_interface']['interface_name']
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} {{{iiface}}}')
+ else:
+ iiface = rule_conf['inbound_interface']['interface_group']
+ if iiface[0] == '!':
+ operator = '!='
+ iiface = iiface[1:]
+ output.append(f'iifname {operator} @I_{iiface}')
if 'outbound_interface' in rule_conf:
- oiface = rule_conf['outbound_interface']
- output.append(f'oifname {oiface}')
+ operator = ''
+ if 'interface_name' in rule_conf['outbound_interface']:
+ oiface = rule_conf['outbound_interface']['interface_name']
+ if oiface[0] == '!':
+ operator = '!='
+ oiface = oiface[1:]
+ output.append(f'oifname {operator} {{{oiface}}}')
+ else:
+ oiface = rule_conf['outbound_interface']['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': '<'}
@@ -282,6 +335,9 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
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}}}')
@@ -293,7 +349,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'ipsec' in rule_conf:
if 'match_ipsec' in rule_conf['ipsec']:
output.append('meta ipsec == 1')
- if 'match_non_ipsec' in rule_conf['ipsec']:
+ if 'match_none' in rule_conf['ipsec']:
output.append('meta ipsec == 0')
if 'fragment' in rule_conf:
@@ -313,7 +369,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'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
+ 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']))
@@ -327,21 +383,43 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
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 '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"]}')
+
+
output.append('counter')
if 'set' in rule_conf:
output.append(parse_policy_set(rule_conf['set'], def_suffix))
if 'action' in rule_conf:
- output.append(nft_action(rule_conf['action']))
+ # Change action=return to action=action
+ # #output.append(nft_action(rule_conf['action']))
+ 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}')
+
else:
output.append('return')
- output.append(f'comment "{fw_name}-{rule_id}"')
+ output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"')
return " ".join(output)
def parse_tcp_flags(flags):
@@ -373,6 +451,9 @@ def parse_time(time):
def parse_policy_set(set_conf, def_suffix):
out = []
+ if 'connection_mark' in set_conf:
+ conn_mark = set_conf['connection_mark']
+ out.append(f'ct mark set {conn_mark}')
if 'dscp' in set_conf:
dscp = set_conf['dscp']
out.append(f'ip{def_suffix} dscp set {dscp}')
@@ -466,11 +547,12 @@ def geoip_update(firewall, force=False):
# Map country codes to set names
for codes, path in dict_search_recursive(firewall, 'country_code'):
- set_name = f'GEOIP_CC_{path[1]}_{path[3]}'
- if path[0] == 'name':
+ 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_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)