summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/firewall-interface.py186
-rwxr-xr-xsrc/conf_mode/firewall.py193
-rwxr-xr-xsrc/conf_mode/zone_policy.py65
-rwxr-xr-xsrc/migration-scripts/firewall/7-to-888
4 files changed, 145 insertions, 387 deletions
diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py
deleted file mode 100755
index ab1c69259..000000000
--- a/src/conf_mode/firewall-interface.py
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/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()
-
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-NFT_CHAINS = {
- 'in': 'VYOS_FW_FORWARD',
- 'out': 'VYOS_FW_FORWARD',
- 'local': 'VYOS_FW_LOCAL'
-}
-NFT6_CHAINS = {
- 'in': 'VYOS_FW6_FORWARD',
- 'out': 'VYOS_FW6_FORWARD',
- '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_chain(table, chain):
- # Verify firewall applied
- code = run(f'nft list chain {table} {chain}')
- return code == 0
-
-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 not verify_chain('ip filter', f'{NAME_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- 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}"')
-
- if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'):
- raise ConfigError('Firewall did not apply')
-
- return None
-
-def generate(if_firewall):
- return None
-
-def cleanup_rule(table, chain, prefix, ifname, new_name=None):
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- retval = None
- for line in results:
- if f'{prefix}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 state_policy_handle(table, chain):
- # Find any state-policy rule to ensure interface rules are only inserted afterwards
- results = cmd(f'nft -a list chain {table} {chain}').split("\n")
- for line in results:
- if 'jump VYOS_STATE_POLICY' in line:
- handle_search = re.search('handle (\d+)', line)
- if handle_search:
- return handle_search[1]
- return None
-
-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, if_prefix, ifname, f'{NAME_PREFIX}{name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip filter', chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}')
- else:
- cleanup_rule('ip filter', chain, if_prefix, ifname)
-
- ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name')
- if ipv6_name:
- rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}')
-
- if not rule_exists:
- rule_action = 'insert'
- rule_prefix = ''
-
- handle = state_policy_handle('ip6 filter', ipv6_chain)
- if handle:
- rule_action = 'add'
- rule_prefix = f'position {handle}'
-
- run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}')
- else:
- cleanup_rule('ip6 filter', ipv6_chain, if_prefix, 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 f0ea1a1e5..86793ba86 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -26,6 +26,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdiff import get_config_diff, Diff
+# from vyos.configverify import verify_interface_exists
from vyos.firewall import geoip_update
from vyos.firewall import get_ips_domains_dict
from vyos.firewall import nft_add_set_elements
@@ -38,7 +39,7 @@ from vyos.util import cmd
from vyos.util import dict_search_args
from vyos.util import dict_search_recursive
from vyos.util import process_named_running
-from vyos.util import run
+from vyos.util import rc_cmd
from vyos.xml import defaults
from vyos import ConfigError
from vyos import airbag
@@ -47,7 +48,9 @@ airbag.enable()
policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py'
nftables_conf = '/run/nftables.conf'
-nftables_defines_conf = '/run/nftables_defines.conf'
+
+nftables_zone_conf = '/run/nftables_zone.conf'
+nftables6_zone_conf = '/run/nftables_zone6.conf'
sysfs_config = {
'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
@@ -63,28 +66,6 @@ sysfs_config = {
'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
}
-NAME_PREFIX = 'NAME_'
-NAME6_PREFIX = 'NAME6_'
-
-preserve_chains = [
- 'INPUT',
- 'FORWARD',
- 'OUTPUT',
- 'VYOS_FW_FORWARD',
- 'VYOS_FW_LOCAL',
- 'VYOS_FW_OUTPUT',
- 'VYOS_POST_FW',
- 'VYOS_FRAG_MARK',
- 'VYOS_FW6_FORWARD',
- 'VYOS_FW6_LOCAL',
- 'VYOS_FW6_OUTPUT',
- 'VYOS_POST_FW6',
- 'VYOS_FRAG6_MARK'
-]
-
-nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']
-nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']
-
valid_groups = [
'address_group',
'domain_group',
@@ -97,16 +78,6 @@ nested_group_types = [
'port_group', 'ipv6_address_group', 'ipv6_network_group'
]
-group_set_prefix = {
- 'A_': 'address_group',
- 'A6_': 'ipv6_address_group',
- 'D_': 'domain_group',
- 'M_': 'mac_group',
- 'N_': 'network_group',
- 'N6_': 'ipv6_network_group',
- 'P_': 'port_group'
-}
-
snmp_change_type = {
'unknown': 0,
'add': 1,
@@ -117,22 +88,6 @@ 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_firewall_zones(conf):
used_v4 = []
used_v6 = []
@@ -159,6 +114,8 @@ def get_firewall_zones(conf):
ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name')
if ipv6_name:
used_v6.append(ipv6_name)
+ else:
+ return None
return {'name': used_v4, 'ipv6_name': used_v6}
@@ -232,7 +189,6 @@ def get_config(config=None):
firewall['ipv6_name'][ipv6_name])
firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
- firewall['interfaces'] = get_firewall_interfaces(conf)
firewall['zone_policy'] = get_firewall_zones(conf)
if 'config_trap' in firewall and firewall['config_trap'] == 'enable':
@@ -358,109 +314,42 @@ def verify(firewall):
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 name_id.startswith("VZONE"):
- raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix')
-
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 'interface' in firewall:
+ for ifname, if_firewall in firewall['interface'].items():
+ # verify ifname needs to be disabled, dynamic devices come up later
+ # verify_interface_exists(ifname)
- if name and dict_search_args(firewall, 'name', name) == None:
- raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}')
+ 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 ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
- raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}')
+ if name and dict_search_args(firewall, 'name', name) == None:
+ raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}')
- for fw_name, used_names in firewall['zone_policy'].items():
- for name in used_names:
- if dict_search_args(firewall, fw_name, name) == None:
- raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
+ if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None:
+ raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}')
- return None
+ if firewall['zone_policy']:
+ for fw_name, used_names in firewall['zone_policy'].items():
+ for name in used_names:
+ if dict_search_args(firewall, fw_name, name) == None:
+ raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy')
-def cleanup_commands(firewall):
- commands = []
- commands_chains = []
- commands_sets = []
- for table in ['ip filter', 'ip6 filter']:
- name_node = 'name' if table == 'ip filter' else 'ipv6_name'
- chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX
- state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6'
- iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains
-
- geoip_list = []
- if firewall['geoip_updated']:
- geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name'
- geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or []
-
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
-
- if 'nftables' not in obj:
- continue
-
- for item in obj['nftables']:
- if 'chain' in item:
- chain = item['chain']['name']
- if chain in preserve_chains or chain.startswith("VZONE"):
- continue
-
- if chain == state_chain:
- command = 'delete' if 'state_policy' not in firewall else 'flush'
- commands_chains.append(f'{command} chain {table} {chain}')
- elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None:
- commands.append(f'flush chain {table} {chain}')
- else:
- commands_chains.append(f'delete chain {table} {chain}')
-
- if 'rule' in item:
- rule = item['rule']
- chain = rule['chain']
- handle = rule['handle']
-
- if chain in iface_chains:
- target, _ = next(dict_search_recursive(rule['expr'], 'target'))
-
- if target == state_chain and 'state_policy' not in firewall:
- commands.append(f'delete rule {table} {chain} handle {handle}')
-
- if target.startswith(chain_prefix):
- if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None:
- commands.append(f'delete rule {table} {chain} handle {handle}')
-
- if 'set' in item:
- set_name = item['set']['name']
-
- if set_name.startswith('GEOIP_CC_') and set_name in geoip_list:
- commands_sets.append(f'delete set {table} {set_name}')
- continue
-
- if set_name.startswith("RECENT_"):
- commands_sets.append(f'delete set {table} {set_name}')
- continue
-
- for prefix, group_type in group_set_prefix.items():
- if set_name.startswith(prefix):
- group_name = set_name.replace(prefix, "", 1)
- if dict_search_args(firewall, 'group', group_type, group_name) != None:
- commands_sets.append(f'flush set {table} {set_name}')
- else:
- commands_sets.append(f'delete set {table} {set_name}')
- return commands + commands_chains + commands_sets
+ return None
def generate(firewall):
if not os.path.exists(nftables_conf):
firewall['first_install'] = True
- else:
- firewall['cleanup_commands'] = cleanup_commands(firewall)
+
+ if os.path.exists(nftables_zone_conf):
+ firewall['zone_conf'] = nftables_zone_conf
+
+ if os.path.exists(nftables6_zone_conf):
+ firewall['zone6_conf'] = nftables6_zone_conf
render(nftables_conf, 'firewall/nftables.j2', firewall)
return None
@@ -521,26 +410,21 @@ def post_apply_trap(firewall):
cmd(base_cmd + ' '.join(objects))
-def state_policy_rule_exists():
- # Determine if state policy rules already exist in nft
- search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD')
- return 'VYOS_STATE_POLICY' in search_str
-
def resync_policy_route():
# Update policy route as firewall groups were updated
- tmp = run(policy_route_conf_script)
+ tmp, out = rc_cmd(policy_route_conf_script)
if tmp > 0:
- Warning('Failed to re-apply policy route configuration!')
+ Warning(f'Failed to re-apply policy route configuration! {out}')
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}')
+
+ install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
- raise ConfigError('Failed to apply firewall')
+ raise ConfigError(f'Failed to apply firewall: {output}')
# set firewall group domain-group xxx
if 'group' in firewall:
@@ -563,13 +447,6 @@ def apply(firewall):
else:
call('systemctl stop vyos-domain-group-resolve.service')
- if 'state_policy' in firewall and not state_policy_rule_exists():
- for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']:
- cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY')
-
- for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']:
- cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6')
-
apply_sysfs(firewall)
if firewall['policy_resync']:
diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py
index a52c52706..c6ab4e304 100755
--- a/src/conf_mode/zone_policy.py
+++ b/src/conf_mode/zone_policy.py
@@ -21,6 +21,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdiff import get_config_diff
from vyos.template import render
from vyos.util import cmd
from vyos.util import dict_search_args
@@ -30,7 +31,9 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+firewall_conf_script = '/usr/libexec/vyos/conf_mode/firewall.py'
nftables_conf = '/run/nftables_zone.conf'
+nftables6_conf = '/run/nftables_zone6.conf'
def get_config(config=None):
if config:
@@ -47,6 +50,9 @@ def get_config(config=None):
get_first_key=True,
no_tag_node_value_mangle=True)
+ diff = get_config_diff(conf)
+ zone_policy['firewall_changed'] = diff.is_node_changed(['firewall'])
+
if 'zone' in zone_policy:
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
@@ -111,20 +117,12 @@ def verify(zone_policy):
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:
- if 'name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
- raise ConfigError(f'Firewall name "{v4_name}" does not exist')
+ if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name):
+ raise ConfigError(f'Firewall name "{v4_name}" does not exist')
v6_name = dict_search_args(from_conf, 'firewall', 'v6_name')
- if v6_name:
- if 'ipv6_name' not in zone_policy['firewall']:
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
-
- if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name):
- raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
+ if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name):
+ raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
return None
@@ -152,37 +150,11 @@ def get_local_from(zone_policy, local_zone_name):
out[zone] = zone_conf['from'][local_zone_name]
return out
-def cleanup_commands():
- commands = []
- for table in ['ip filter', 'ip6 filter']:
- json_str = cmd(f'nft -t -j list table {table}')
- obj = loads(json_str)
- if 'nftables' not in obj:
- continue
- for item in obj['nftables']:
- if 'rule' in item:
- chain = item['rule']['chain']
- handle = item['rule']['handle']
- if 'expr' not in item['rule']:
- continue
- for expr in item['rule']['expr']:
- target = dict_search_args(expr, 'jump', 'target')
- if not target:
- continue
- if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"):
- commands.append(f'delete rule {table} {chain} handle {handle}')
- for item in obj['nftables']:
- if 'chain' in item:
- if item['chain']['name'].startswith("VZONE"):
- chain = item['chain']['name']
- commands.append(f'delete chain {table} {chain}')
- return commands
-
def generate(zone_policy):
data = zone_policy or {}
- if os.path.exists(nftables_conf): # Check to see if we've run before
- data['cleanup_commands'] = cleanup_commands()
+ if not os.path.exists(nftables_conf):
+ data['first_install'] = True
if 'zone' in data:
for zone, zone_conf in data['zone'].items():
@@ -193,12 +165,19 @@ def generate(zone_policy):
zone_conf['from_local'] = get_local_from(data, zone)
render(nftables_conf, 'zone_policy/nftables.j2', data)
+ render(nftables6_conf, 'zone_policy/nftables6.j2', data)
return None
+def update_firewall():
+ # Update firewall to refresh nftables
+ tmp = run(firewall_conf_script)
+ if tmp > 0:
+ Warning('Failed to update firewall configuration!')
+
def apply(zone_policy):
- install_result = run(f'nft -f {nftables_conf}')
- if install_result != 0:
- raise ConfigError('Failed to apply zone-policy')
+ # If firewall will not update in this commit, we need to call the conf script
+ if not zone_policy['firewall_changed']:
+ update_firewall()
return None
diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8
new file mode 100755
index 000000000..6929e20a5
--- /dev/null
+++ b/src/migration-scripts/firewall/7-to-8
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name>
+
+import re
+
+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)
+
+def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None):
+ if_path = ['interfaces', iftype, ifname]
+ ifname_full = ifname
+
+ if vif:
+ if_path += ['vif', vif]
+ ifname_full = f'{ifname}.{vif}'
+ elif vifs:
+ if_path += ['vif-s', vifs]
+ ifname_full = f'{ifname}.{vifs}'
+ if vifc:
+ if_path += ['vif-c', vifc]
+ ifname_full = f'{ifname}.{vifs}.{vifc}'
+
+ if not config.exists(if_path + ['firewall']):
+ return
+
+ if not config.exists(['firewall', 'interface']):
+ config.set(['firewall', 'interface'])
+ config.set_tag(['firewall', 'interface'])
+
+ config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full])
+ config.delete(if_path + ['firewall'])
+
+for iftype in config.list_nodes(['interfaces']):
+ for ifname in config.list_nodes(['interfaces', iftype]):
+ migrate_interface(config, iftype, ifname)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif']):
+ for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']):
+ migrate_interface(config, iftype, ifname, vif=vif)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s']):
+ for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']):
+ migrate_interface(config, iftype, ifname, vifs=vifs)
+
+ if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']):
+ migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc)
+
+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)