summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2022-01-11 01:10:59 +0100
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2022-01-11 14:49:12 +0100
commit6cf5767524b8519f86981943ab71ff288bf77d67 (patch)
treeb0fb06a0d51ad6c3ed39bfc631642c62b61d52cf /src/conf_mode
parente389729f4de84ce3f32e1a0cdb471c919d7d7807 (diff)
downloadvyos-1x-6cf5767524b8519f86981943ab71ff288bf77d67.tar.gz
vyos-1x-6cf5767524b8519f86981943ab71ff288bf77d67.zip
policy: T2199: Refactor policy route script for better error handling
* Migrates all policy route references from `ipv6-route` to `route6` * Update test config `dialup-router-medium-vpn` to test migration of `ipv6-route` to `route6`
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/policy-route-interface.py8
-rwxr-xr-xsrc/conf_mode/policy-route.py169
2 files changed, 127 insertions, 50 deletions
diff --git a/src/conf_mode/policy-route-interface.py b/src/conf_mode/policy-route-interface.py
index e81135a74..1108aebe6 100755
--- a/src/conf_mode/policy-route-interface.py
+++ b/src/conf_mode/policy-route-interface.py
@@ -52,7 +52,7 @@ def verify(if_policy):
if not if_policy:
return None
- for route in ['route', 'ipv6_route']:
+ for route in ['route', 'route6']:
if route in if_policy:
if route not in if_policy['policy']:
raise ConfigError('Policy route not configured')
@@ -71,7 +71,7 @@ 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'oifname "{ifname}"' in line:
+ 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
@@ -98,8 +98,8 @@ def apply(if_policy):
else:
cleanup_rule('ip mangle', route_chain, ifname)
- if 'ipv6_route' in if_policy:
- name = 'VYOS_PBR6_' + if_policy['ipv6_route']
+ if 'route6' in if_policy:
+ name = 'VYOS_PBR6_' + if_policy['route6']
rule_exists = cleanup_rule('ip6 mangle', ipv6_route_chain, ifname, name)
if not rule_exists:
diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py
index 9edab4b47..c5904309f 100755
--- a/src/conf_mode/policy-route.py
+++ b/src/conf_mode/policy-route.py
@@ -31,6 +31,35 @@ airbag.enable()
mark_offset = 0x7FFFFFFF
nftables_conf = '/run/nftables_policy.conf'
+preserve_chains = [
+ 'VYOS_PBR_PREROUTING',
+ 'VYOS_PBR_POSTROUTING',
+ 'VYOS_PBR6_PREROUTING',
+ 'VYOS_PBR6_POSTROUTING'
+]
+
+valid_groups = [
+ 'address_group',
+ 'network_group',
+ 'port_group'
+]
+
+def get_policy_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 'policy' in if_conf:
+ output[prefix + ifname] = if_conf['policy']
+ 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
@@ -38,61 +67,117 @@ def get_config(config=None):
conf = Config()
base = ['policy']
- if not conf.exists(base + ['route']) and not conf.exists(base + ['ipv6-route']):
- return None
-
policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True,
no_tag_node_value_mangle=True)
+ policy['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ policy['interfaces'] = get_policy_interfaces(conf)
+
return policy
-def verify(policy):
- # bail out early - looks like removal from running config
- if not policy:
- return None
+def verify_rule(policy, rule_conf, ipv6):
+ icmp = 'icmp' if not ipv6 else 'icmpv6'
+ if icmp in rule_conf:
+ icmp_defined = False
+ if 'type_name' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name')
+ if 'code' in rule_conf[icmp]:
+ icmp_defined = True
+ if 'type' not in rule_conf[icmp]:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined')
+ if 'type' in rule_conf[icmp]:
+ icmp_defined = True
+
+ if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp:
+ raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP')
+ if 'set' in rule_conf:
+ if 'tcp_mss' in rule_conf['set']:
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if not tcp_flags or 'SYN' not in tcp_flags.split(","):
+ raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
+ if 'tcp' in rule_conf:
+ if 'flags' in rule_conf['tcp']:
+ if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp':
+ raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP')
+
+ 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
+ error_group = fw_group.replace("_", "-")
+ group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name)
+
+ if group_obj is None:
+ raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule')
+
+ if not group_obj:
+ print(f'WARNING: {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')
+def verify(policy):
for route in ['route', 'route6']:
+ ipv6 = route == 'route6'
if route in policy:
for name, pol_conf in policy[route].items():
if 'rule' in pol_conf:
- for rule_id, rule_conf in pol_conf.items():
- icmp = 'icmp' if route == 'route' else 'icmpv6'
- if icmp in rule_conf:
- icmp_defined = False
- if 'type_name' in rule_conf[icmp]:
- icmp_defined = True
- if 'code' in rule_conf[icmp] or 'type' in rule_conf[icmp]:
- raise ConfigError(f'{name} rule {rule_id}: Cannot use ICMP type/code with ICMP type-name')
- if 'code' in rule_conf[icmp]:
- icmp_defined = True
- if 'type' not in rule_conf[icmp]:
- raise ConfigError(f'{name} rule {rule_id}: ICMP code can only be defined if ICMP type is defined')
- if 'type' in rule_conf[icmp]:
- icmp_defined = True
-
- if icmp_defined and 'protocol' not in rule_conf or rule_conf['protocol'] != icmp:
- raise ConfigError(f'{name} rule {rule_id}: ICMP type/code or type-name can only be defined if protocol is ICMP')
- if 'set' in rule_conf:
- if 'tcp_mss' in rule_conf['set']:
- tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
- if not tcp_flags or 'SYN' not in tcp_flags.split(","):
- raise ConfigError(f'{name} rule {rule_id}: TCP SYN flag must be set to modify TCP-MSS')
- if 'tcp' in rule_conf:
- if 'flags' in rule_conf['tcp']:
- if 'protocol' not in rule_conf or rule_conf['protocol'] != 'tcp':
- raise ConfigError(f'{name} rule {rule_id}: TCP flags can only be set if protocol is set to TCP')
+ for rule_id, rule_conf in pol_conf['rule'].items():
+ verify_rule(policy, rule_conf, ipv6)
+
+ for ifname, if_policy in policy['interfaces'].items():
+ name = dict_search_args(if_policy, 'route')
+ ipv6_name = dict_search_args(if_policy, 'route6')
+ if name and not dict_search_args(policy, 'route', name):
+ raise ConfigError(f'Policy route "{name}" is still referenced on interface {ifname}')
+
+ if ipv6_name and not dict_search_args(policy, 'route6', ipv6_name):
+ raise ConfigError(f'Policy route6 "{ipv6_name}" is still referenced on interface {ifname}')
return None
-def generate(policy):
- if not policy:
- if os.path.exists(nftables_conf):
- os.unlink(nftables_conf)
- return None
+def cleanup_commands(policy):
+ commands = []
+ for table in ['ip mangle', 'ip6 mangle']:
+ 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:
+ chain = item['chain']['name']
+ if not chain.startswith("VYOS_PBR"):
+ continue
+ if chain not in preserve_chains:
+ if table == 'ip mangle' and dict_search_args(policy, 'route', chain.replace("VYOS_PBR_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ elif table == 'ip6 mangle' and dict_search_args(policy, 'route6', chain.replace("VYOS_PBR6_", "", 1)):
+ commands.append(f'flush chain {table} {chain}')
+ else:
+ commands.append(f'delete chain {table} {chain}')
+ return commands
+def generate(policy):
if not os.path.exists(nftables_conf):
policy['first_install'] = True
+ else:
+ policy['cleanup_commands'] = cleanup_commands(policy)
render(nftables_conf, 'firewall/nftables-policy.tmpl', policy)
return None
@@ -124,14 +209,6 @@ def cleanup_table_marks():
cmd(f'ip rule del fwmark {fwmark} table {table}')
def apply(policy):
- if not policy or 'first_install' not in policy:
- run(f'nft flush table ip mangle')
- run(f'nft flush table ip6 mangle')
-
- if not policy:
- cleanup_table_marks()
- return None
-
install_result = run(f'nft -f {nftables_conf}')
if install_result == 1:
raise ConfigError('Failed to apply policy based routing')