diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/firewall.py | 37 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 7 | ||||
| -rwxr-xr-x | src/conf_mode/nat.py | 15 | ||||
| -rw-r--r-- | src/etc/sysctl.d/30-vyos-router.conf | 10 | ||||
| -rwxr-xr-x | src/init/vyos-router | 2 | ||||
| -rwxr-xr-x | src/op_mode/firewall.py | 114 | 
6 files changed, 103 insertions, 82 deletions
| diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index c3b1ee015..769cc598f 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,7 +26,7 @@ from vyos.config import Config  from vyos.configdict import node_changed  from vyos.configdiff import get_config_diff, Diff  from vyos.configdep import set_dependents, call_dependents -# from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_interface_exists  from vyos.firewall import fqdn_config_parse  from vyos.firewall import geoip_update  from vyos.template import render @@ -38,6 +38,7 @@ from vyos.utils.process import process_named_running  from vyos.utils.process import rc_cmd  from vyos import ConfigError  from vyos import airbag +  airbag.enable()  nat_conf_script = 'nat.py' @@ -100,7 +101,7 @@ def geoip_updated(conf, firewall):          elif (path[0] == 'ipv6'):              set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'              out['ipv6_name'].append(set_name) -             +          updated = True      if 'delete' in node_diff: @@ -140,6 +141,14 @@ def get_config(config=None):      fqdn_config_parse(firewall) +    firewall['flowtable_enabled'] = False +    flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') +    if flow_offload and 'disable' not in flow_offload: +        for offload_type in ('software', 'hardware'): +            if dict_search_args(flow_offload, offload_type, 'interface'): +                firewall['flowtable_enabled'] = True +                break +      return firewall  def verify_rule(firewall, rule_conf, ipv6): @@ -327,6 +336,14 @@ def verify(firewall):                          for rule_id, rule_conf in name_conf['rule'].items():                              verify_rule(firewall, rule_conf, True) +    # Verify flow offload options +    flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload') +    for offload_type in ('software', 'hardware'): +        interfaces = dict_search_args(flow_offload, offload_type, 'interface') or [] +        for interface in interfaces: +            # nft will raise an error when adding a non-existent interface to a flowtable +            verify_interface_exists(interface) +      return None  def generate(firewall): @@ -336,13 +353,15 @@ def generate(firewall):      # Determine if conntrack is needed      firewall['ipv4_conntrack_action'] = 'return'      firewall['ipv6_conntrack_action'] = 'return' - -    for rules, path in dict_search_recursive(firewall, 'rule'): -        if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): -            if path[0] == 'ipv4': -                firewall['ipv4_conntrack_action'] = 'accept' -            elif path[0] == 'ipv6': -                firewall['ipv6_conntrack_action'] = 'accept' +    if firewall['flowtable_enabled']:  # Netfilter's flowtable offload requires conntrack +        firewall['ipv4_conntrack_action'] = 'accept' +        firewall['ipv6_conntrack_action'] = 'accept' +    else:  # Check if conntrack is needed by firewall rules +        for proto in ('ipv4', 'ipv6'): +            for rules, _ in dict_search_recursive(firewall.get(proto, {}), 'rule'): +                if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): +                    firewall[f'{proto}_conntrack_action'] = 'accept' +                    break      render(nftables_conf, 'firewall/nftables.j2', firewall)      return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 1d0feb56f..9f4de990c 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -344,9 +344,6 @@ def verify(openvpn):              if v6_subnets > 1:                  raise ConfigError('Cannot specify more than 1 IPv6 server subnet') -            if v6_subnets > 0 and v4_subnets == 0: -                raise ConfigError('IPv6 server requires an IPv4 server subnet') -              for subnet in tmp:                  if is_ipv4(subnet):                      subnet = IPv4Network(subnet) @@ -388,6 +385,10 @@ def verify(openvpn):                          for v4PoolNet in v4PoolNets:                              if IPv4Address(client['ip'][0]) in v4PoolNet:                                  print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.') +            # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream) +            for subnet in (dict_search('server.subnet', openvpn) or []): +                if is_ipv6(subnet): +                    raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.')          for subnet in (dict_search('server.subnet', openvpn) or []):              if is_ipv6(subnet): diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 08e96f10b..e37a7011c 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -195,11 +195,10 @@ def verify(nat):      if dict_search('source.rule', nat):          for rule, config in dict_search('source.rule', nat).items():              err_msg = f'Source NAT configuration error in rule {rule}:' -            if 'outbound_interface' not in config: -                raise ConfigError(f'{err_msg} outbound-interface not specified') -            if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): -                Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') +            if 'outbound_interface' in config: +                if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): +                    Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')              if not dict_search('translation.address', config) and not dict_search('translation.port', config):                  if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -218,11 +217,9 @@ def verify(nat):          for rule, config in dict_search('destination.rule', nat).items():              err_msg = f'Destination NAT configuration error in rule {rule}:' -            if 'inbound_interface' not in config: -                raise ConfigError(f'{err_msg}\n' \ -                                  'inbound-interface not specified') -            elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): -                Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') +            if 'inbound_interface' in config: +                if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): +                    Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')              if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:                  if 'exclude' not in config and 'backend' not in config['load_balance']: diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index ad43390bb..fcdc1b21d 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -98,15 +98,6 @@ net.ipv6.route.skip_notify_on_dev_down=1  # Default value of 20 seems to interfere with larger OSPF and VRRP setups  net.ipv4.igmp_max_memberships = 512 -# Increase default garbage collection thresholds -net.ipv4.neigh.default.gc_thresh1 = 1024 -net.ipv4.neigh.default.gc_thresh2 = 4096 -net.ipv4.neigh.default.gc_thresh3 = 8192 -# -net.ipv6.neigh.default.gc_thresh1 = 1024 -net.ipv6.neigh.default.gc_thresh2 = 4096 -net.ipv6.neigh.default.gc_thresh3 = 8192 -  # Enable global RFS (Receive Flow Steering) configuration. RFS is inactive  # until explicitly configured at the interface level  net.core.rps_sock_flow_entries = 32768 @@ -114,3 +105,4 @@ net.core.rps_sock_flow_entries = 32768  # Congestion control  net.core.default_qdisc=fq  net.ipv4.tcp_congestion_control=bbr + diff --git a/src/init/vyos-router b/src/init/vyos-router index 1bbb9c869..9ef1fa335 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -346,6 +346,8 @@ start ()      ${vyos_conf_scripts_dir}/system-login.py || log_failure_msg "could not reset system login"      ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files"      ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files" +    ${vyos_conf_scripts_dir}/system-ip.py || log_failure_msg "could not reset system IPv4 options" +    ${vyos_conf_scripts_dir}/system-ipv6.py || log_failure_msg "could not reset system IPv6 options"      ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem"      ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 11cbd977d..3434707ec 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -24,27 +24,39 @@ from vyos.config import Config  from vyos.utils.process import cmd  from vyos.utils.dict import dict_search_args -def get_config_firewall(conf, hook=None, priority=None, ipv6=False): +def get_config_firewall(conf, family=None, hook=None, priority=None):      config_path = ['firewall'] -    if hook: -        config_path += ['ipv6' if ipv6 else 'ipv4', hook] -        if priority: -            config_path += [priority] +    if family: +        config_path += [family] +        if hook: +            config_path += [hook] +            if priority: +                config_path += [priority]      firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'),                                  get_first_key=True, no_tag_node_value_mangle=True)      return firewall -def get_nftables_details(hook, priority, ipv6=False): -    suffix = '6' if ipv6 else '' -    aux = 'IPV6_' if ipv6 else '' -    name_prefix = 'NAME6_' if ipv6 else 'NAME_' +def get_nftables_details(family, hook, priority): +    if family == 'ipv6': +        suffix = 'ip6' +        name_prefix = 'NAME6_' +        aux='IPV6_' +    elif family == 'ipv4': +        suffix = 'ip' +        name_prefix = 'NAME_' +        aux='' +    else: +        suffix = 'bridge' +        name_prefix = 'NAME_' +        aux='' +      if hook == 'name' or hook == 'ipv6-name': -        command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}' +        command = f'sudo nft list chain {suffix} vyos_filter {name_prefix}{priority}'      else:          up_hook = hook.upper() -        command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}' +        command = f'sudo nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}'      try:          results = cmd(command) @@ -68,11 +80,10 @@ def get_nftables_details(hook, priority, ipv6=False):          out[rule_id] = rule      return out -def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_id=None): -    ip_str = 'IPv6' if ipv6 else 'IPv4' -    print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {priority}"\n') +def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=None): +    print(f'\n---------------------------------\n{family} Firewall "{hook} {priority}"\n') -    details = get_nftables_details(hook, priority, ipv6) +    details = get_nftables_details(family, hook, priority)      rows = []      if 'rule' in firewall_conf: @@ -103,11 +114,10 @@ def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_          header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']          print(tabulate.tabulate(rows, header) + '\n') -def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_rule_id=None): -    ip_str = 'IPv6' if ipv6 else 'IPv4' -    print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n') +def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule_id=None): +    print(f'\n---------------------------------\n{family} Firewall "{hook} {prior}"\n') -    details = get_nftables_details(hook, prior, ipv6) +    details = get_nftables_details(family, hook, prior)      rows = []      if 'rule' in prior_conf: @@ -210,8 +220,8 @@ def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_              row.append('0')              row.append('0')          row.append(prior_conf['default_action']) -        row.append('any') # Source -        row.append('any') # Dest +        row.append('any')   # Source +        row.append('any')   # Dest          row.append('any')   # inbound-interface          row.append('any')   # outbound-interface          rows.append(row) @@ -229,15 +239,11 @@ def show_firewall():      if not firewall:          return -    if 'ipv4' in firewall: -        for hook, hook_conf in firewall['ipv4'].items(): -            for prior, prior_conf in firewall['ipv4'][hook].items(): -                output_firewall_name(hook, prior, prior_conf, ipv6=False) - -    if 'ipv6' in firewall: -        for hook, hook_conf in firewall['ipv6'].items(): -            for prior, prior_conf in firewall['ipv6'][hook].items(): -                output_firewall_name(hook, prior, prior_conf, ipv6=True) +    for family in ['ipv4', 'ipv6', 'bridge']: +        if family in firewall: +            for hook, hook_conf in firewall[family].items(): +                for prior, prior_conf in firewall[family][hook].items(): +                    output_firewall_name(family, hook, prior, prior_conf)  def show_firewall_family(family):      print(f'Rulesets {family} Information') @@ -245,31 +251,28 @@ def show_firewall_family(family):      conf = Config()      firewall = get_config_firewall(conf) -    if not firewall: +    if not firewall or family not in firewall:          return      for hook, hook_conf in firewall[family].items():          for prior, prior_conf in firewall[family][hook].items(): -            if family == 'ipv6': -                output_firewall_name(hook, prior, prior_conf, ipv6=True) -            else: -                output_firewall_name(hook, prior, prior_conf, ipv6=False) +            output_firewall_name(family, hook, prior, prior_conf) -def show_firewall_name(hook, priority, ipv6=False): +def show_firewall_name(family, hook, priority):      print('Ruleset Information')      conf = Config() -    firewall = get_config_firewall(conf, hook, priority, ipv6) +    firewall = get_config_firewall(conf, family, hook, priority)      if firewall: -        output_firewall_name(hook, priority, firewall, ipv6) +        output_firewall_name(family, hook, priority, firewall) -def show_firewall_rule(hook, priority, rule_id, ipv6=False): +def show_firewall_rule(family, hook, priority, rule_id):      print('Rule Information')      conf = Config() -    firewall = get_config_firewall(conf, hook, priority, ipv6) +    firewall = get_config_firewall(conf, family, hook, priority)      if firewall: -        output_firewall_name(hook, priority, firewall, ipv6, rule_id) +        output_firewall_name(family, hook, priority, firewall, rule_id)  def show_firewall_group(name=None):      conf = Config() @@ -369,6 +372,7 @@ def show_summary():      header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References']      v4_out = []      v6_out = [] +    br_out = []      if 'ipv4' in firewall:          for hook, hook_conf in firewall['ipv4'].items(): @@ -382,6 +386,12 @@ def show_summary():                  description = prior_conf.get('description', '')                  v6_out.append([hook, prior, description]) +    if 'bridge' in firewall: +        for hook, hook_conf in firewall['bridge'].items(): +            for prior, prior_conf in firewall['bridge'][hook].items(): +                description = prior_conf.get('description', '') +                br_out.append([hook, prior, description]) +      if v6_out:          print('\nIPv6 Ruleset:\n')          print(tabulate.tabulate(v6_out, header) + '\n') @@ -390,6 +400,10 @@ def show_summary():          print('\nIPv4 Ruleset:\n')          print(tabulate.tabulate(v4_out, header) + '\n') +    if br_out: +        print('\nBridge Ruleset:\n') +        print(tabulate.tabulate(br_out, header) + '\n') +      show_firewall_group()  def show_statistics(): @@ -401,15 +415,11 @@ def show_statistics():      if not firewall:          return -    if 'ipv4' in firewall: -        for hook, hook_conf in firewall['ipv4'].items(): -            for prior, prior_conf in firewall['ipv4'][hook].items(): -                output_firewall_name_statistics(hook,prior, prior_conf, ipv6=False) - -    if 'ipv6' in firewall: -        for hook, hook_conf in firewall['ipv6'].items(): -            for prior, prior_conf in firewall['ipv6'][hook].items(): -                output_firewall_name_statistics(hook,prior, prior_conf, ipv6=True) +    for family in ['ipv4', 'ipv6', 'bridge']: +        if family in firewall: +            for hook, hook_conf in firewall[family].items(): +                for prior, prior_conf in firewall[family][hook].items(): +                    output_firewall_name_statistics(family, hook,prior, prior_conf)  if __name__ == '__main__':      parser = argparse.ArgumentParser() @@ -425,9 +435,9 @@ if __name__ == '__main__':      if args.action == 'show':          if not args.rule: -            show_firewall_name(args.hook, args.priority, args.ipv6) +            show_firewall_name(args.family, args.hook, args.priority)          else: -            show_firewall_rule(args.hook, args.priority, args.rule, args.ipv6) +            show_firewall_rule(args.family, args.hook, args.priority, args.rule)      elif args.action == 'show_all':          show_firewall()      elif args.action == 'show_family': | 
