summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-28 12:03:21 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2021-12-06 21:20:49 +0100
commitfdeba8da3e99256fe449e331d0b833a941315226 (patch)
treec6181ec7b84796a52a72d61e7d89ad6974f6f5f6 /src
parent025f0609cea8591e93b8cb4a7d0256e43e23323b (diff)
downloadvyos-1x-fdeba8da3e99256fe449e331d0b833a941315226.tar.gz
vyos-1x-fdeba8da3e99256fe449e331d0b833a941315226.zip
firewall: T2199: Migrate firewall to XML/Python
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/conntrack.py39
-rwxr-xr-xsrc/conf_mode/firewall-interface.py146
-rwxr-xr-xsrc/conf_mode/firewall.py267
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py89
-rwxr-xr-xsrc/conf_mode/nat.py13
-rwxr-xr-xsrc/conf_mode/nat66.py12
-rwxr-xr-xsrc/conf_mode/protocols_nhrp.py27
-rwxr-xr-xsrc/migration-scripts/firewall/6-to-772
-rwxr-xr-xsrc/validators/ip-protocol2
9 files changed, 565 insertions, 102 deletions
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index 68877f794..c65ef9540 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -15,11 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import re
from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.firewall import find_nftables_rule
+from vyos.firewall import remove_nftables_rule
from vyos.util import cmd
from vyos.util import run
from vyos.util import process_named_running
@@ -43,8 +46,8 @@ module_map = {
'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
},
'nfs' : {
- 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 111 --jump CT --helper rpc',
- 'VYATTA_CT_HELPER --table raw --proto udp --dport 111 --jump CT --helper rpc'],
+ 'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return',
+ 'ct helper set "rpc_udp" udp dport "{111}" return']
},
'pptp' : {
'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
@@ -53,9 +56,7 @@ module_map = {
'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
},
'sqlnet' : {
- 'iptables' : ['VYATTA_CT_HELPER --table raw --proto tcp --dport 1521 --jump CT --helper tns',
- 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1525 --jump CT --helper tns',
- 'VYATTA_CT_HELPER --table raw --proto tcp --dport 1536 --jump CT --helper tns'],
+ 'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return']
},
'tftp' : {
'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
@@ -93,6 +94,17 @@ def generate(conntrack):
return None
+def find_nftables_ct_rule(rule):
+ helper_search = re.search('ct helper set "(\w+)"', rule)
+ if helper_search:
+ rule = helper_search[1]
+ return find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule])
+
+def find_remove_rule(rule):
+ handle = find_nftables_ct_rule(rule)
+ if handle:
+ remove_nftables_rule('raw', 'VYOS_CT_HELPER', handle)
+
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
# modules we need to either insmod or rmmod the helpers.
@@ -103,20 +115,17 @@ def apply(conntrack):
# Only remove the module if it's loaded
if os.path.exists(f'/sys/module/{mod}'):
cmd(f'rmmod {mod}')
- if 'iptables' in module_config:
- for rule in module_config['iptables']:
- # Only install iptables rule if it does not exist
- tmp = run(f'iptables --check {rule}')
- if tmp == 0: cmd(f'iptables --delete {rule}')
+ if 'nftables' in module_config:
+ for rule in module_config['nftables']:
+ find_remove_rule(rule)
else:
if 'ko' in module_config:
for mod in module_config['ko']:
cmd(f'modprobe {mod}')
- if 'iptables' in module_config:
- for rule in module_config['iptables']:
- # Only install iptables rule if it does not exist
- tmp = run(f'iptables --check {rule}')
- if tmp > 0: cmd(f'iptables --insert {rule}')
+ if 'nftables' in module_config:
+ for rule in module_config['nftables']:
+ if not find_nftables_ct_rule(rule):
+ cmd(f'nft insert rule ip raw VYOS_CT_HELPER {rule}')
if process_named_running('conntrackd'):
# Reload conntrack-sync daemon to fetch new sysctl values
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
new file mode 100755
index 000000000..3a17dc5a4
--- /dev/null
+++ b/src/conf_mode/firewall-interface.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 argv
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import leaf_node_changed
+from vyos.ifconfig import Section
+from vyos.template import render
+from vyos.util import cmd
+from vyos.util import dict_search_args
+from vyos.util import run
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+NFT_CHAINS = {
+ 'in': 'VYOS_FW_IN',
+ 'out': 'VYOS_FW_OUT',
+ 'local': 'VYOS_FW_LOCAL'
+}
+NFT6_CHAINS = {
+ 'in': 'VYOS_FW6_IN',
+ 'out': 'VYOS_FW6_OUT',
+ 'local': 'VYOS_FW6_LOCAL'
+}
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ ifname = argv[1]
+ ifpath = Section.get_config_path(ifname)
+ if_firewall_path = f'interfaces {ifpath} firewall'
+
+ if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ if_firewall['ifname'] = ifname
+ if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ return if_firewall
+
+def verify(if_firewall):
+ # bail out early - looks like removal from running config
+ if not if_firewall:
+ return None
+
+ for direction in ['in', 'out', 'local']:
+ if direction in if_firewall:
+ if 'name' in if_firewall[direction]:
+ name = if_firewall[direction]['name']
+
+ if 'name' not in if_firewall['firewall']:
+ raise ConfigError('Firewall name not configured')
+
+ if name not in if_firewall['firewall']['name']:
+ raise ConfigError(f'Invalid firewall name "{name}"')
+
+ if 'ipv6_name' in if_firewall[direction]:
+ name = if_firewall[direction]['ipv6_name']
+
+ if 'ipv6_name' not in if_firewall['firewall']:
+ raise ConfigError('Firewall ipv6-name not configured')
+
+ if name not in if_firewall['firewall']['ipv6_name']:
+ raise ConfigError(f'Invalid firewall ipv6-name "{name}"')
+
+ return None
+
+def generate(if_firewall):
+ return None
+
+def cleanup_rule(table, chain, ifname, new_name=None):
+ results = cmd(f'nft -a list chain {table} {chain}').split("\n")
+ retval = None
+ for line in results:
+ if f'ifname "{ifname}"' in line:
+ if new_name and f'jump {new_name}' in line:
+ # new_name is used to clear rules for any previously referenced chains
+ # returns true when rule exists and doesn't need to be created
+ retval = True
+ continue
+
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ run(f'nft delete rule {table} {chain} handle {handle_search[1]}')
+ return retval
+
+def apply(if_firewall):
+ ifname = if_firewall['ifname']
+
+ for direction in ['in', 'out', 'local']:
+ chain = NFT_CHAINS[direction]
+ ipv6_chain = NFT6_CHAINS[direction]
+ if_prefix = 'i' if direction in ['in', 'local'] else 'o'
+
+ name = dict_search_args(if_firewall, direction, 'name')
+ if name:
+ rule_exists = cleanup_rule('ip filter', chain, ifname, name)
+
+ if not rule_exists:
+ run(f'nft insert rule ip filter {chain} {if_prefix}ifname {ifname} counter jump {name}')
+ else:
+ cleanup_rule('ip filter', chain, ifname)
+
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+ if ipv6_name:
+ rule_exists = cleanup_rule('ip6 filter', ipv6_chain, ifname, ipv6_name)
+
+ if not rule_exists:
+ run(f'nft insert rule ip6 filter {ipv6_chain} {if_prefix}ifname {ifname} counter jump {ipv6_name}')
+ else:
+ cleanup_rule('ip6 filter', ipv6_chain, ifname)
+
+ 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/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 8e6ce5b14..5ac48c9ba 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -16,50 +16,295 @@
import os
+from glob import glob
+from json import loads
from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
-from vyos.configdict import node_changed
-from vyos.configdict import leaf_node_changed
+from vyos.configdiff import get_config_diff, Diff
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 process_named_running
+from vyos.util import run
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
-from pprint import pprint
airbag.enable()
+nftables_conf = '/run/nftables.conf'
-def get_config(config=None):
+sysfs_config = {
+ 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
+ 'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'enable': '0', 'disable': '1'},
+ 'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route'},
+ 'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects'},
+ 'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'enable': '0', 'disable': '-1'},
+ 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'},
+ 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'},
+ 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'},
+ 'source_validation': {'sysfs': '/proc/sys/net/ipv4/conf/*/rp_filter', 'disable': '0', 'strict': '1', 'loose': '2'},
+ 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'},
+ 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
+}
+
+preserve_chains = [
+ 'INPUT',
+ 'FORWARD',
+ 'OUTPUT',
+ 'VYOS_FW_IN',
+ 'VYOS_FW_OUT',
+ 'VYOS_FW_LOCAL',
+ 'VYOS_FW_OUTPUT',
+ 'VYOS_POST_FW',
+ 'VYOS_FRAG_MARK',
+ 'VYOS_FW6_IN',
+ 'VYOS_FW6_OUT',
+ 'VYOS_FW6_LOCAL',
+ 'VYOS_FW6_OUTPUT',
+ 'VYOS_POST_FW6',
+ 'VYOS_FRAG6_MARK'
+]
+
+valid_groups = [
+ 'address_group',
+ 'network_group',
+ 'port_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 get_firewall_interfaces(conf):
+ out = {}
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ def find_interfaces(iftype_conf, output={}, prefix=''):
+ for ifname, if_conf in iftype_conf.items():
+ if 'firewall' in if_conf:
+ output[prefix + ifname] = if_conf['firewall']
+ for vif in ['vif', 'vif_s', 'vif_c']:
+ if vif in if_conf:
+ output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.'))
+ return output
+ for iftype, iftype_conf in interfaces.items():
+ out.update(find_interfaces(iftype_conf))
+ return out
+
+def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['nfirewall']
+ base = ['firewall']
+
+ if not conf.exists(base):
+ return {}
+
firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
- pprint(firewall)
+ default_values = defaults(base)
+ firewall = dict_merge(default_values, firewall)
+
+ firewall['interfaces'] = get_firewall_interfaces(conf)
+
+ if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
+ diff = get_config_diff(conf)
+ firewall['trap_diff'] = diff.get_child_nodes_diff_str(base)
+ firewall['trap_targets'] = conf.get_config_dict(['service', 'snmp', 'trap-target'],
+ key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
return firewall
+def verify_rule(firewall, rule_conf, ipv6):
+ if 'action' not in rule_conf:
+ raise ConfigError('Rule action must be 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 'ipsec' in rule_conf:
+ if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']):
+ raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"')
+
+ if 'recent' in rule_conf:
+ if not {'count', 'time'} <= set(rule_conf['recent']):
+ raise ConfigError('Recent "count" and "time" values must be defined')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ side_conf = rule_conf[side]
+
+ if 'group' in side_conf:
+ if {'address_group', 'network_group'} <= set(side_conf['group']):
+ raise ConfigError('Only one address-group or network-group can be specified')
+
+ for group in valid_groups:
+ if group in side_conf['group']:
+ group_name = side_conf['group'][group]
+
+ fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+
+ if not dict_search_args(firewall, 'group', fw_group):
+ error_group = fw_group.replace("_", "-")
+ raise ConfigError(f'Group defined in rule but {error_group} is not configured')
+
+ if group_name not in firewall['group'][fw_group]:
+ error_group = group.replace("_", "-")
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
+
+ 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')
+
def verify(firewall):
# bail out early - looks like removal from running config
if not firewall:
return None
+ if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
+ if not firewall['trap_targets']:
+ raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined')
+
+ for name in ['name', 'ipv6_name']:
+ if name in firewall:
+ for name_id, name_conf in firewall[name].items():
+ if name_id in preserve_chains:
+ raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS')
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ verify_rule(firewall, rule_conf, name == 'ipv6_name')
+
+ for ifname, if_firewall in firewall['interfaces'].items():
+ for direction in ['in', 'out', 'local']:
+ name = dict_search_args(if_firewall, direction, 'name')
+ ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
+
+ if name and not dict_search_args(firewall, 'name', name):
+ raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and not dict_search_args(firewall, 'ipv6_name', ipv6_name):
+ raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+
return None
+def cleanup_commands(firewall):
+ commands = []
+ for table in ['ip filter', 'ip6 filter']:
+ json_str = cmd(f'nft -j list table {table}')
+ obj = loads(json_str)
+ if 'nftables' not in obj:
+ continue
+ for item in obj['nftables']:
+ if 'chain' in item:
+ if item['chain']['name'] not in preserve_chains:
+ chain = item['chain']['name']
+ if table == 'ip filter' and dict_search_args(firewall, 'name', chain):
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 filter' and dict_search_args(firewall, 'ipv6_name', chain):
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+
def generate(firewall):
- if not firewall:
- return None
+ if not os.path.exists(nftables_conf):
+ firewall['first_install'] = True
+ else:
+ firewall['cleanup_commands'] = cleanup_commands(firewall)
+ render(nftables_conf, 'firewall/nftables.tmpl', firewall)
return None
-def apply(firewall):
- if not firewall:
+def apply_sysfs(firewall):
+ for name, conf in sysfs_config.items():
+ paths = glob(conf['sysfs'])
+ value = None
+
+ if name in firewall:
+ conf_value = firewall[name]
+
+ if conf_value in conf:
+ value = conf[conf_value]
+ elif conf_value == 'enable':
+ value = '1'
+ elif conf_value == 'disable':
+ value = '0'
+
+ if value:
+ for path in paths:
+ with open(path, 'w') as f:
+ f.write(value)
+
+def post_apply_trap(firewall):
+ if 'first_install' in firewall:
return None
+ if 'config_trap' not in firewall or firewall['config_trap'] != 'enable':
+ return None
+
+ if not process_named_running('snmpd'):
+ return None
+
+ trap_username = os.getlogin()
+
+ for host, target_conf in firewall['trap_targets'].items():
+ community = target_conf['community'] if 'community' in target_conf else 'public'
+ port = int(target_conf['port']) if 'port' in target_conf else 162
+
+ base_cmd = f'snmptrap -v2c -c {community} {host}:{port} 0 {snmp_trap_mib}::{snmp_trap_name} '
+
+ for change_type, changes in firewall['trap_diff'].items():
+ for path_str, value in changes.items():
+ objects = [
+ f'mgmtEventUser s "{trap_username}"',
+ f'mgmtEventSource i {snmp_event_source}',
+ f'mgmtEventType i {snmp_change_type[change_type]}'
+ ]
+
+ if change_type == 'add':
+ objects.append(f'mgmtEventCurrCfg s "{path_str} {value}"')
+ elif change_type == 'delete':
+ objects.append(f'mgmtEventPrevCfg s "{path_str} {value}"')
+ elif change_type == 'change':
+ objects.append(f'mgmtEventPrevCfg s "{path_str} {value[0]}"')
+ objects.append(f'mgmtEventCurrCfg s "{path_str} {value[1]}"')
+
+ cmd(base_cmd + ' '.join(objects))
+
+def apply(firewall):
+ if 'first_install' in firewall:
+ run('nfct helper add rpc inet tcp')
+ run('nfct helper add rpc inet udp')
+ run('nfct helper add tns inet tcp')
+
+ install_result = run(f'nft -f {nftables_conf}')
+ if install_result == 1:
+ raise ConfigError('Failed to apply firewall')
+
+ if 'state_policy' in firewall:
+ for chain in ['INPUT', 'OUTPUT', 'FORWARD']:
+ cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
+ cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
+
+ apply_sysfs(firewall)
+
+ post_apply_trap(firewall)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 0a4559ade..1b036a53f 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -40,10 +40,10 @@ default_captured_packet_size = 128
default_netflow_version = '9'
default_sflow_agentip = 'auto'
uacctd_conf_path = '/etc/pmacct/uacctd.conf'
-iptables_nflog_table = 'raw'
-iptables_nflog_chain = 'VYATTA_CT_PREROUTING_HOOK'
-egress_iptables_nflog_table = 'mangle'
-egress_iptables_nflog_chain = 'FORWARD'
+nftables_nflog_table = 'raw'
+nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK'
+egress_nftables_nflog_table = 'inet mangle'
+egress_nftables_nflog_chain = 'FORWARD'
# helper functions
# check if node exists and return True if this is true
@@ -75,62 +75,61 @@ def _sflow_default_agentip(config):
# return nothing by default
return None
-# get iptables rule dict for chain in table
-def _iptables_get_nflog(chain, table):
+# get nftables rule dict for chain in table
+def _nftables_get_nflog(chain, table):
# define list with rules
rules = []
# prepare regex for parsing rules
- rule_pattern = "^-A (?P<rule_definition>{0} (\-i|\-o) (?P<interface>[\w\.\*\-]+).*--comment FLOW_ACCOUNTING_RULE.* -j NFLOG.*$)".format(chain)
+ rule_pattern = '[io]ifname "(?P<interface>[\w\.\*\-]+)".*handle (?P<handle>[\d]+)'
rule_re = re.compile(rule_pattern)
- for iptables_variant in ['iptables', 'ip6tables']:
- # run iptables, save output and split it by lines
- iptables_command = f'{iptables_variant} -t {table} -S {chain}'
- tmp = cmd(iptables_command, message='Failed to get flows list')
+ # run nftables, save output and split it by lines
+ nftables_command = f'nft -a list chain {table} {chain}'
+ tmp = cmd(nftables_command, message='Failed to get flows list')
- # parse each line and add information to list
- for current_rule in tmp.splitlines():
- current_rule_parsed = rule_re.search(current_rule)
- if current_rule_parsed:
- rules.append({ 'interface': current_rule_parsed.groupdict()["interface"], 'iptables_variant': iptables_variant, 'table': table, 'rule_definition': current_rule_parsed.groupdict()["rule_definition"] })
+ # parse each line and add information to list
+ for current_rule in tmp.splitlines():
+ if 'FLOW_ACCOUNTING_RULE' not in current_rule:
+ continue
+ current_rule_parsed = rule_re.search(current_rule)
+ if current_rule_parsed:
+ groups = current_rule_parsed.groupdict()
+ rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] })
# return list with rules
return rules
-# modify iptables rules
-def _iptables_config(configured_ifaces, direction):
- # define list of iptables commands to modify settings
- iptable_commands = []
- iptables_chain = iptables_nflog_chain
- iptables_table = iptables_nflog_table
+# modify nftables rules
+def _nftables_config(configured_ifaces, direction):
+ # define list of nftables commands to modify settings
+ nftable_commands = []
+ nftables_chain = nftables_nflog_chain
+ nftables_table = nftables_nflog_table
if direction == "egress":
- iptables_chain = egress_iptables_nflog_chain
- iptables_table = egress_iptables_nflog_table
+ nftables_chain = egress_nftables_nflog_chain
+ nftables_table = egress_nftables_nflog_table
# prepare extended list with configured interfaces
configured_ifaces_extended = []
for iface in configured_ifaces:
- configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'iptables' })
- configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'ip6tables' })
+ configured_ifaces_extended.append({ 'iface': iface })
- # get currently configured interfaces with iptables rules
- active_nflog_rules = _iptables_get_nflog(iptables_chain, iptables_table)
+ # get currently configured interfaces with nftables rules
+ active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table)
# compare current active list with configured one and delete excessive interfaces, add missed
active_nflog_ifaces = []
for rule in active_nflog_rules:
- iptables = rule['iptables_variant']
interface = rule['interface']
if interface not in configured_ifaces:
table = rule['table']
- rule = rule['rule_definition']
- iptable_commands.append(f'{iptables} -t {table} -D {rule}')
+ handle = rule['handle']
+ nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}')
else:
active_nflog_ifaces.append({
'iface': interface,
- 'iptables_variant': iptables,
})
# do not create new rules for already configured interfaces
@@ -141,16 +140,12 @@ def _iptables_config(configured_ifaces, direction):
# create missed rules
for iface_extended in configured_ifaces_extended:
iface = iface_extended['iface']
- iptables = iface_extended['iptables_variant']
- iptables_op = "-i"
- if direction == "egress":
- iptables_op = "-o"
+ iface_prefix = "o" if direction == "egress" else "i"
+ rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {default_captured_packet_size} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"'
+ nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}')
- rule_definition = f'{iptables_chain} {iptables_op} {iface} -m comment --comment FLOW_ACCOUNTING_RULE -j NFLOG --nflog-group 2 --nflog-size {default_captured_packet_size} --nflog-threshold 100'
- iptable_commands.append(f'{iptables} -t {iptables_table} -I {rule_definition}')
-
- # change iptables
- for command in iptable_commands:
+ # change nftables
+ for command in nftable_commands:
cmd(command, raising=ConfigError)
@@ -367,18 +362,18 @@ def apply(config):
# run command to start or stop flow-accounting
cmd(command, raising=ConfigError, message='Failed to start/stop flow-accounting')
- # configure iptables rules for defined interfaces
+ # configure nftables rules for defined interfaces
if config['interfaces']:
- _iptables_config(config['interfaces'], 'ingress')
+ _nftables_config(config['interfaces'], 'ingress')
# configure egress the same way if configured otherwise remove it
if config['enable-egress']:
- _iptables_config(config['interfaces'], 'egress')
+ _nftables_config(config['interfaces'], 'egress')
else:
- _iptables_config([], 'egress')
+ _nftables_config([], 'egress')
else:
- _iptables_config([], 'ingress')
- _iptables_config([], 'egress')
+ _nftables_config([], 'ingress')
+ _nftables_config([], 'egress')
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 59939d0fb..62fb9abad 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -93,7 +93,6 @@ def get_config(config=None):
nat[direction]['rule'][rule] = dict_merge(default_values,
nat[direction]['rule'][rule])
-
# read in current nftable (once) for further processing
tmp = cmd('nft -j list table raw')
nftable_json = json.loads(tmp)
@@ -106,9 +105,9 @@ def get_config(config=None):
nat['helper_functions'] = 'remove'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
return nat
@@ -119,10 +118,10 @@ def get_config(config=None):
nat['helper_functions'] = 'add'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
return nat
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index fb376a434..d0b6d27ac 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -79,9 +79,9 @@ def get_config(config=None):
if not conf.exists(base):
nat['helper_functions'] = 'remove'
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
nat['deleted'] = ''
return nat
@@ -92,10 +92,10 @@ def get_config(config=None):
nat['helper_functions'] = 'add'
# Retrieve current table handler positions
- nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE')
- nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
- nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE')
- nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK')
+ nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
+ nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
else:
nat['helper_functions'] = 'has'
diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py
index 12dacdba0..7eeb5cd30 100755
--- a/src/conf_mode/protocols_nhrp.py
+++ b/src/conf_mode/protocols_nhrp.py
@@ -16,6 +16,8 @@
from vyos.config import Config
from vyos.configdict import node_changed
+from vyos.firewall import find_nftables_rule
+from vyos.firewall import remove_nftables_rule
from vyos.template import render
from vyos.util import process_named_running
from vyos.util import run
@@ -88,24 +90,19 @@ def generate(nhrp):
def apply(nhrp):
if 'tunnel' in nhrp:
for tunnel, tunnel_conf in nhrp['tunnel'].items():
- if 'source_address' in tunnel_conf:
- chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
- source_address = tunnel_conf['source_address']
+ if 'source_address' in nhrp['if_tunnel'][tunnel]:
+ comment = f'VYOS_NHRP_{tunnel}'
+ source_address = nhrp['if_tunnel'][tunnel]['source_address']
- chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
- if not chain_exists:
- run(f'sudo iptables --new {chain}')
- run(f'sudo iptables --append {chain} -p gre -s {source_address} -d 224.0.0.0/4 -j DROP')
- run(f'sudo iptables --append {chain} -j RETURN')
- run(f'sudo iptables --insert OUTPUT 2 -j {chain}')
+ rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4'])
+ if not rule_handle:
+ run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"')
for tunnel in nhrp['del_tunnels']:
- chain = f'VYOS_NHRP_{tunnel}_OUT_HOOK'
- chain_exists = run(f'sudo iptables --check {chain} -j RETURN') == 0
- if chain_exists:
- run(f'sudo iptables --delete OUTPUT -j {chain}')
- run(f'sudo iptables --flush {chain}')
- run(f'sudo iptables --delete-chain {chain}')
+ comment = f'VYOS_NHRP_{tunnel}'
+ rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"'])
+ if rule_handle:
+ remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle)
action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop'
run(f'systemctl {action} opennhrp')
diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7
new file mode 100755
index 000000000..4a4097d56
--- /dev/null
+++ b/src/migration-scripts/firewall/6-to-7
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+# T2199: Remove unavailable nodes due to XML/Python implementation using nftables
+# monthdays: nftables does not have a monthdays equivalent
+# utc: nftables userspace uses localtime and calculates the UTC offset automatically
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+from vyos.ifconfig import Section
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['firewall']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['name']):
+ for name in config.list_nodes(base + ['name']):
+ if config.exists(base + ['name', name, 'rule']):
+ for rule in config.list_nodes(base + ['name', name, 'rule']):
+ rule_time = base + ['name', name, 'rule', rule, 'time']
+
+ if config.exists(rule_time + ['monthdays']):
+ config.delete(rule_time + ['monthdays'])
+
+ if config.exists(rule_time + ['utc']):
+ config.delete(rule_time + ['utc'])
+
+if config.exists(base + ['ipv6-name']):
+ for name in config.list_nodes(base + ['ipv6-name']):
+ if config.exists(base + ['ipv6-name', name, 'rule']):
+ for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']):
+ rule_time = base + ['ipv6-name', name, 'rule', rule, 'time']
+
+ if config.exists(rule_time + ['monthdays']):
+ config.delete(rule_time + ['monthdays'])
+
+ if config.exists(rule_time + ['utc']):
+ config.delete(rule_time + ['utc'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/validators/ip-protocol b/src/validators/ip-protocol
index 078f8e319..7898fa6d0 100755
--- a/src/validators/ip-protocol
+++ b/src/validators/ip-protocol
@@ -31,7 +31,7 @@ if __name__ == '__main__':
pattern = "!?\\b(all|ip|hopopt|icmp|igmp|ggp|ipencap|st|tcp|egp|igp|pup|udp|" \
"tcp_udp|hmp|xns-idp|rdp|iso-tp4|dccp|xtp|ddp|idpr-cmtp|ipv6|" \
- "ipv6-route|ipv6-frag|idrp|rsvp|gre|esp|ah|skip|ipv6-icmp|" \
+ "ipv6-route|ipv6-frag|idrp|rsvp|gre|esp|ah|skip|ipv6-icmp|icmpv6|" \
"ipv6-nonxt|ipv6-opts|rspf|vmtp|eigrp|ospf|ax.25|ipip|etherip|" \
"encap|99|pim|ipcomp|vrrp|l2tp|isis|sctp|fc|mobility-header|" \
"udplite|mpls-in-ip|manet|hip|shim6|wesp|rohc)\\b"