diff options
52 files changed, 843 insertions, 533 deletions
| diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 47579e1c6..933894447 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,6 +21,9 @@ the box, please use [x]  <!-- All submitted PRs must be linked to a Task on Phabricator. -->  * https://vyos.dev/Txxxx +## Related PR(s) +<!-- Link here any PRs in other repositories that are required by this PR --> +  ## Component(s) name  <!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... --> @@ -37,6 +40,14 @@ like this  ```  --> +## Smoketest result +<!-- Provide the output of the smoketest +``` +$ /usr/libexec/vyos/tests/smoke/cli/test_xxx_feature.py +test_01_simple_options (__main__.TestFeature.test_01_simple_options) ... ok +``` +--> +  ## Checklist:  <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->  <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json index 08732bd4c..a433c2522 100644 --- a/data/config-mode-dependencies/vyos-1x.json +++ b/data/config-mode-dependencies/vyos-1x.json @@ -1,6 +1,9 @@  { -  "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]}, +  "firewall": {"conntrack": ["conntrack"], "group_resync": ["conntrack", "nat", "policy-route"]},    "http_api": {"https": ["https"]}, +  "load_balancing_wan": {"conntrack": ["conntrack"]}, +  "nat": {"conntrack": ["conntrack"]}, +  "nat66": {"conntrack": ["conntrack"]},    "pki": {             "ethernet": ["interfaces-ethernet"],             "openvpn": ["interfaces-openvpn"], diff --git a/data/templates/aws/override_aws_gwlbtun.conf.j2 b/data/templates/aws/override_aws_gwlbtun.conf.j2 new file mode 100644 index 000000000..4c566d852 --- /dev/null +++ b/data/templates/aws/override_aws_gwlbtun.conf.j2 @@ -0,0 +1,36 @@ +{% set args = [] %} +{% if script.on_create is vyos_defined %} +{%     set _ = args.append("-c " + script.on_create) %} +{% endif %} +{% if script.on_destroy is vyos_defined %} +{%     set _ = args.append("-r " + script.on_destroy) %} +{% endif %} + +{% if status.port is vyos_defined %} +{%     set _ = args.append("-p " + status.port) %} +{% endif %} + +{% if threads.tunnel is vyos_defined %} +{%     set _ = args.append("--tunthreads " + threads.tunnel) %} +{% endif %} +{% if threads.tunnel_affinity is vyos_defined %} +{%     set _ = args.append("--tunaffinity " + threads.tunnel_affinity) %} +{% endif %} + +{% if threads.udp is vyos_defined %} +{%     set _ = args.append("--udpthreads " + threads.udp) %} +{% endif %} +{% if threads.udp_affinity is vyos_defined %} +{%     set _ = args.append("--udpaffinity " + threads.udp_affinity) %} +{% endif %} + +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service + +[Service] +EnvironmentFile= +ExecStart=/usr/bin/gwlbtun {{ args | join(' ') }} +CapabilityBoundingSet=CAP_NET_ADMIN +Restart=always +RestartSec=10 diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2 index 3a5b5a87c..895f61a55 100644 --- a/data/templates/conntrack/nftables-ct.j2 +++ b/data/templates/conntrack/nftables-ct.j2 @@ -2,16 +2,11 @@  {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %} -{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %} - -# we first flush all chains and render the content from scratch - this makes -# any delta check obsolete -flush chain raw {{ nft_ct_ignore_name }} -flush chain raw {{ nft_ct_timeout_name }} - -table raw { -    chain {{ nft_ct_ignore_name }} { +{% if first_install is not vyos_defined %} +delete table ip vyos_conntrack +{% endif %} +table ip vyos_conntrack { +    chain VYOS_CT_IGNORE {  {% if ignore.ipv4.rule is vyos_defined %}  {%     for rule, rule_config in ignore.ipv4.rule.items() %}          # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -20,7 +15,7 @@ table raw {  {% endif %}          return      } -    chain {{ nft_ct_timeout_name }} { +    chain VYOS_CT_TIMEOUT {  {% if timeout.custom.rule is vyos_defined %}  {%     for rule, rule_config in timeout.custom.rule.items() %}          # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -29,14 +24,81 @@ table raw {          return      } -{{ group_tmpl.groups(firewall_group, False, True) }} -} +    chain PREROUTING { +        type filter hook prerouting priority -300; policy accept; +{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %} +        counter jump VYOS_CT_HELPER +{% endif %} +        counter jump VYOS_CT_IGNORE +        counter jump VYOS_CT_TIMEOUT +        counter jump FW_CONNTRACK +        counter jump NAT_CONNTRACK +        counter jump WLB_CONNTRACK +        notrack +    } + +    chain OUTPUT { +        type filter hook output priority -300; policy accept; +{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %} +        counter jump VYOS_CT_HELPER +{% endif %} +        counter jump VYOS_CT_IGNORE +        counter jump VYOS_CT_TIMEOUT +        counter jump FW_CONNTRACK +        counter jump NAT_CONNTRACK +{% if wlb_local_action %} +        counter jump WLB_CONNTRACK +{% endif %} +        notrack +    } + +    ct helper rpc_tcp { +        type "rpc" protocol tcp; +    } + +    ct helper rpc_udp { +        type "rpc" protocol udp; +    } + +    ct helper tns_tcp { +        type "tns" protocol tcp; +    } + +    chain VYOS_CT_HELPER { +{% for module, module_conf in module_map.items() %} +{%     if modules[module] is vyos_defined %} +{%         if 'nftables' in module_conf %} +{%             for rule in module_conf.nftables %} +        {{ rule }} +{%             endfor %} +{%         endif %} +{%     endif %} +{% endfor %} +        return +    } + +    chain FW_CONNTRACK { +        {{ ipv4_firewall_action }} +    } + +    chain NAT_CONNTRACK { +        {{ ipv4_nat_action }} +    } + +    chain WLB_CONNTRACK { +        {{ wlb_action }} +    } -flush chain ip6 raw {{ nft_ct_ignore_name }} -flush chain ip6 raw {{ nft_ct_timeout_name }} +{% if firewall.group is vyos_defined %} +{{ group_tmpl.groups(firewall.group, False, True) }} +{% endif %} +} -table ip6 raw { -    chain {{ nft_ct_ignore_name }} { +{% if first_install is not vyos_defined %} +delete table ip6 vyos_conntrack +{% endif %} +table ip6 vyos_conntrack { +    chain VYOS_CT_IGNORE {  {% if ignore.ipv6.rule is vyos_defined %}  {%     for rule, rule_config in ignore.ipv6.rule.items() %}          # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -45,7 +107,7 @@ table ip6 raw {  {% endif %}          return      } -    chain {{ nft_ct_timeout_name }} { +    chain VYOS_CT_TIMEOUT {  {% if timeout.custom.rule is vyos_defined %}  {%     for rule, rule_config in timeout.custom.rule.items() %}          # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }} @@ -54,5 +116,64 @@ table ip6 raw {          return      } -{{ group_tmpl.groups(firewall_group, True, True) }} +    chain PREROUTING { +        type filter hook prerouting priority -300; policy accept; +{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %} +        counter jump VYOS_CT_HELPER +{% endif %} +        counter jump VYOS_CT_IGNORE +        counter jump VYOS_CT_TIMEOUT +        counter jump FW_CONNTRACK +        counter jump NAT_CONNTRACK +        notrack +    } + +    chain OUTPUT { +        type filter hook output priority -300; policy accept; +{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %} +        counter jump VYOS_CT_HELPER +{% endif %} +        counter jump VYOS_CT_IGNORE +        counter jump VYOS_CT_TIMEOUT +        counter jump FW_CONNTRACK +        counter jump NAT_CONNTRACK +        notrack +    } + +    ct helper rpc_tcp { +        type "rpc" protocol tcp; +    } + +    ct helper rpc_udp { +        type "rpc" protocol udp; +    } + +    ct helper tns_tcp { +        type "tns" protocol tcp; +    } + +    chain VYOS_CT_HELPER { +{% for module, module_conf in module_map.items() %} +{%     if modules[module] is vyos_defined %} +{%         if 'nftables' in module_conf %} +{%             for rule in module_conf.nftables %} +        {{ rule }} +{%             endfor %} +{%         endif %} +{%     endif %} +{% endfor %} +        return +    } + +    chain FW_CONNTRACK { +        {{ ipv6_firewall_action }} +    } + +    chain NAT_CONNTRACK { +        {{ ipv6_nat_action }} +    } + +{% if firewall.group is vyos_defined %} +{{ group_tmpl.groups(firewall.group, True, True) }} +{% endif %}  } diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 index 3446a9d1b..421daf1df 100644 --- a/data/templates/dns-dynamic/ddclient.conf.j2 +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -59,11 +59,8 @@ use=no            {# ddclient default ('ip') results in confusing warning messag  {%                 endif %}  {%                 for host in config.host_name if config.host_name is vyos_defined %}  {%                     set ip_suffixes = ['v4', 'v6'] if config.ip_version == 'both' -                                                      else (['v6'] if config.ip_version == 'ipv6' else ['']) %} +                                                      else [config.ip_version[2:]] %} {# 'ipvX' -> 'vX' #}  # Web service dynamic DNS configuration for {{ name }}: [{{ config.protocol }}, {{ host }}] -{# For ipv4 only setup or legacy ipv6 setup, don't append 'new-style' compliant suffix -   ('usev4', 'ifv4', 'webv4' etc.) to the properties and instead live through the -   deprecation warnings for better compatibility with most ddclient protocols. #}  {{ render_config(host, address, service_cfg.web_options, ip_suffixes,                   protocol=config.protocol, server=config.server, zone=config.zone,                   login=config.username, password=config.password) }} diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2 index dcf28da88..4254f6a0e 100644 --- a/data/templates/firewall/nftables-nat.j2 +++ b/data/templates/firewall/nftables-nat.j2 @@ -2,27 +2,6 @@  {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% if helper_functions is vyos_defined('remove') %} -{# NAT if going to be disabled - remove rules and targets from nftables #} -{%     set base_command = 'delete rule ip raw' %} -{{ base_command }} PREROUTING handle {{ pre_ct_ignore }} -{{ base_command }} OUTPUT     handle {{ out_ct_ignore }} -{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} -{{ base_command }} OUTPUT     handle {{ out_ct_conntrack }} - -delete chain ip raw NAT_CONNTRACK - -{% elif helper_functions is vyos_defined('add') %} -{# NAT if enabled - add targets to nftables #} -add chain ip raw NAT_CONNTRACK -add rule ip raw NAT_CONNTRACK counter accept -{%     set base_command = 'add rule ip raw' %} -{{ base_command }} PREROUTING position {{ pre_ct_ignore }}    counter jump VYOS_CT_HELPER -{{ base_command }} OUTPUT     position {{ out_ct_ignore }}    counter jump VYOS_CT_HELPER -{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -{{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} -  {% if first_install is not vyos_defined %}  delete table ip vyos_nat  {% endif %} diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2 index 27b3eec88..67eb2c109 100644 --- a/data/templates/firewall/nftables-nat66.j2 +++ b/data/templates/firewall/nftables-nat66.j2 @@ -1,22 +1,5 @@  #!/usr/sbin/nft -f -{% if helper_functions is vyos_defined('remove') %} -{# NAT if going to be disabled - remove rules and targets from nftables #} -{%     set base_command = 'delete rule ip6 raw' %} -{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }} -{{ base_command }} OUTPUT handle {{ out_ct_conntrack }} - -delete chain ip6 raw NAT_CONNTRACK - -{% elif helper_functions is vyos_defined('add') %} -{# NAT if enabled - add targets to nftables #} -add chain ip6 raw NAT_CONNTRACK -add rule ip6 raw NAT_CONNTRACK counter accept -{%     set base_command = 'add rule ip6 raw' %} -{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK -{{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK -{% endif %} -  {% if first_install is not vyos_defined %}  delete table ip6 vyos_nat  {% endif %} diff --git a/data/templates/firewall/nftables-offload.j2 b/data/templates/firewall/nftables-offload.j2 index 6afcd79f7..a893e05b2 100644 --- a/data/templates/firewall/nftables-offload.j2 +++ b/data/templates/firewall/nftables-offload.j2 @@ -1,11 +1,9 @@ -{% macro render_flowtable(name, devices, priority='filter', hardware_offload=false, with_counter=true) %} -flowtable {{ name }} { -    hook ingress priority {{ priority }}; devices = { {{ devices | join(', ') }} }; -{% if hardware_offload %} -    flags offload; +{% macro flowtable(name, config) %} +    flowtable VYOS_FLOWTABLE_{{ name }} { +        hook ingress priority 0; devices = { {{ config.interface | join(', ') }} }; +{% if config.offload is vyos_defined('hardware') %} +        flags offload;  {% endif %} -{% if with_counter %} -    counter -{% endif %} -} +        counter +    }  {% endmacro %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 723c9c3a2..75800ee3d 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -2,19 +2,12 @@  {% import 'firewall/nftables-defines.j2' as group_tmpl %}  {% import 'firewall/nftables-bridge.j2' as bridge_tmpl %} -{% import 'firewall/nftables-offload.j2' as offload %} - -flush chain raw FW_CONNTRACK -flush chain ip6 raw FW_CONNTRACK +{% import 'firewall/nftables-offload.j2' as offload_tmpl %}  flush chain raw vyos_global_rpfilter  flush chain ip6 raw vyos_global_rpfilter  table raw { -    chain FW_CONNTRACK { -        {{ ipv4_conntrack_action }} -    } -      chain vyos_global_rpfilter {  {% if global_options.source_validation is vyos_defined('loose') %}          fib saddr oif 0 counter drop @@ -26,10 +19,6 @@ table raw {  }  table ip6 raw { -    chain FW_CONNTRACK { -        {{ ipv6_conntrack_action }} -    } -      chain vyos_global_rpfilter {  {% if global_options.ipv6_source_validation is vyos_defined('loose') %}          fib saddr oif 0 counter drop @@ -45,6 +34,12 @@ delete table ip vyos_filter  {% endif %}  table ip vyos_filter {  {% if ipv4 is vyos_defined %} +{%     if flowtable is vyos_defined %} +{%         for name, flowtable_conf in flowtable.items() %} +{{ offload_tmpl.flowtable(name, flowtable_conf) }} +{%         endfor %} +{%     endif %} +  {%     set ns = namespace(sets=[]) %}  {%     if ipv4.forward is vyos_defined %}  {%         for prior, conf in ipv4.forward.items() %} @@ -164,6 +159,12 @@ delete table ip6 vyos_filter  {% endif %}  table ip6 vyos_filter {  {% if ipv6 is vyos_defined %} +{%     if flowtable is vyos_defined %} +{%         for name, flowtable_conf in flowtable.items() %} +{{ offload_tmpl.flowtable(name, flowtable_conf) }} +{%         endfor %} +{%     endif %} +  {%     set ns = namespace(sets=[]) %}  {%     if ipv6.forward is vyos_defined %}  {%         for prior, conf in ipv6.forward.items() %} @@ -266,31 +267,7 @@ table ip6 vyos_filter {  {% if first_install is not vyos_defined %}  delete table bridge vyos_filter  {% endif %} -{% if bridge is vyos_defined %}  table bridge vyos_filter {  {{ bridge_tmpl.bridge(bridge) }}  {{ group_tmpl.groups(group, False, False) }}  } -{% endif %} -{{ group_tmpl.groups(group, True) }} - -table inet vyos_offload -delete table inet vyos_offload -table inet vyos_offload { -{% if flowtable_enabled %} -{%     if global_options.flow_offload.hardware.interface is vyos_defined %} -    {{- offload.render_flowtable('VYOS_FLOWTABLE_hardware', global_options.flow_offload.hardware.interface | list, priority='filter - 2', hardware_offload=true) }} -    chain VYOS_OFFLOAD_hardware { -        type filter hook forward priority filter - 2; policy accept; -        ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_hardware -    } -{%     endif %} -{%     if global_options.flow_offload.software.interface is vyos_defined %} -    {{- offload.render_flowtable('VYOS_FLOWTABLE_software', global_options.flow_offload.software.interface | list, priority='filter - 1') }} -    chain VYOS_OFFLOAD_software { -        type filter hook forward priority filter - 1; policy accept; -        ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_software -    } -{%     endif %} -{% endif %} -} diff --git a/data/templates/high-availability/10-override.conf.j2 b/data/templates/high-availability/10-override.conf.j2 index d1cb25581..c153f09b4 100644 --- a/data/templates/high-availability/10-override.conf.j2 +++ b/data/templates/high-availability/10-override.conf.j2 @@ -1,5 +1,5 @@  ### Autogenerated by ${vyos_conf_scripts_dir}/high-availability.py ### -{% set snmp = '' if vrrp.disable_snmp is vyos_defined else '--snmp' %} +{% set snmp = '--snmp' if vrrp.snmp is vyos_defined else '' %}  [Unit]  After=vyos-router.service  # Only start if there is our configuration file - remove Debian default diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf index 7e258e6f1..cd7d5011f 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -9,6 +9,7 @@ table ip nat {  }  table inet mangle { +    # Used by system flow-accounting      chain FORWARD {          type filter hook forward priority -150; policy accept;      } @@ -28,61 +29,9 @@ table raw {          counter jump vyos_global_rpfilter      } -    chain PREROUTING { +    # Used by system flow-accounting +    chain VYOS_PREROUTING_HOOK {          type filter hook prerouting priority -300; policy accept; -        counter jump VYOS_CT_IGNORE -        counter jump VYOS_CT_TIMEOUT -        counter jump VYOS_CT_PREROUTING_HOOK -        counter jump FW_CONNTRACK -        notrack -    } - -    chain OUTPUT { -        type filter hook output priority -300; policy accept; -        counter jump VYOS_CT_IGNORE -        counter jump VYOS_CT_TIMEOUT -        counter jump VYOS_CT_OUTPUT_HOOK -        counter jump FW_CONNTRACK -        notrack -    } - -    ct helper rpc_tcp { -        type "rpc" protocol tcp; -    } - -    ct helper rpc_udp { -        type "rpc" protocol udp; -    } - -    ct helper tns_tcp { -        type "tns" protocol tcp; -    } - -    chain VYOS_CT_HELPER { -        ct helper set "rpc_tcp" tcp dport {111} return -        ct helper set "rpc_udp" udp dport {111} return -        ct helper set "tns_tcp" tcp dport {1521,1525,1536} return -        return -    } - -    chain VYOS_CT_IGNORE { -        return -    } - -    chain VYOS_CT_TIMEOUT { -        return -    } - -    chain VYOS_CT_PREROUTING_HOOK { -        return -    } - -    chain VYOS_CT_OUTPUT_HOOK { -        return -    } - -    chain FW_CONNTRACK { -        return      }  } @@ -100,60 +49,8 @@ table ip6 raw {          counter jump vyos_global_rpfilter      } -    chain PREROUTING { +    # Used by system flow-accounting +    chain VYOS_PREROUTING_HOOK {          type filter hook prerouting priority -300; policy accept; -        counter jump VYOS_CT_IGNORE -        counter jump VYOS_CT_TIMEOUT -        counter jump VYOS_CT_PREROUTING_HOOK -        counter jump FW_CONNTRACK -        notrack -    } - -    chain OUTPUT { -        type filter hook output priority -300; policy accept; -        counter jump VYOS_CT_IGNORE -        counter jump VYOS_CT_TIMEOUT -        counter jump VYOS_CT_OUTPUT_HOOK -        counter jump FW_CONNTRACK -        notrack -    } - -    ct helper rpc_tcp { -        type "rpc" protocol tcp; -    } - -    ct helper rpc_udp { -        type "rpc" protocol udp; -    } - -    ct helper tns_tcp { -        type "tns" protocol tcp; -    } - -    chain VYOS_CT_HELPER { -        ct helper set "rpc_tcp" tcp dport {111} return -        ct helper set "rpc_udp" udp dport {111} return -        ct helper set "tns_tcp" tcp dport {1521,1525,1536} return -        return -    } - -    chain VYOS_CT_IGNORE { -        return -    } - -    chain VYOS_CT_TIMEOUT { -        return -    } - -    chain VYOS_CT_PREROUTING_HOOK { -        return -    } - -    chain VYOS_CT_OUTPUT_HOOK { -        return -    } - -    chain FW_CONNTRACK { -        return      }  } diff --git a/debian/control b/debian/control index ee45a5fe3..735733956 100644 --- a/debian/control +++ b/debian/control @@ -36,6 +36,7 @@ Depends:    accel-ppp,    auditd,    avahi-daemon, +  aws-gwlbtun,    beep,    bmon,    bsdmainutils, diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 8e462f3eb..81e6b89ea 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -7,6 +7,46 @@      </properties>      <children>        #include <include/firewall/global-options.xml.i> +      <tagNode name="flowtable"> +        <properties> +          <help>Flowtable</help> +          <constraint> +            <regex>[a-zA-Z0-9][\w\-\.]*</regex> +          </constraint> +        </properties> +        <children> +          #include <include/generic-description.xml.i> +          <leafNode name="interface"> +            <properties> +              <help>Interfaces to use this flowtable</help> +              <completionHelp> +                <script>${vyos_completion_dir}/list_interfaces</script> +              </completionHelp> +              <multi/> +            </properties> +          </leafNode> +          <leafNode name="offload"> +            <properties> +              <help>Offloading method</help> +              <completionHelp> +                <list>hardware software</list> +              </completionHelp> +              <valueHelp> +                <format>hardware</format> +                <description>Hardware offload</description> +              </valueHelp> +              <valueHelp> +                <format>software</format> +                <description>Software offload</description> +              </valueHelp> +              <constraint> +                <regex>(hardware|software)</regex> +              </constraint> +            </properties> +            <defaultValue>software</defaultValue> +          </leafNode> +        </children> +      </tagNode>        <node name="group">          <properties>            <help>Firewall group</help> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 47a772d04..aa23888a4 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -12,10 +12,10 @@            <help>Virtual Router Redundancy Protocol settings</help>          </properties>          <children> -          <leafNode name="disable-snmp"> +          <leafNode name="snmp">              <properties>                <valueless/> -              <help>Disable SNMP</help> +              <help>Enable SNMP</help>              </properties>            </leafNode>            <node name="global-parameters"> diff --git a/interface-definitions/include/firewall/action-forward.xml.i b/interface-definitions/include/firewall/action-forward.xml.i new file mode 100644 index 000000000..f61e51887 --- /dev/null +++ b/interface-definitions/include/firewall/action-forward.xml.i @@ -0,0 +1,45 @@ +<!-- include start from firewall/action-forward.xml.i --> +<leafNode name="action"> +  <properties> +    <help>Rule action</help> +    <completionHelp> +      <list>accept continue jump reject return drop queue offload</list> +    </completionHelp> +    <valueHelp> +      <format>accept</format> +      <description>Accept matching entries</description> +    </valueHelp> +    <valueHelp> +      <format>continue</format> +      <description>Continue parsing next rule</description> +    </valueHelp> +    <valueHelp> +      <format>jump</format> +      <description>Jump to another chain</description> +    </valueHelp> +    <valueHelp> +      <format>reject</format> +      <description>Reject matching entries</description> +    </valueHelp> +    <valueHelp> +      <format>return</format> +      <description>Return from the current chain and continue at the next rule of the last chain</description> +    </valueHelp> +    <valueHelp> +      <format>drop</format> +      <description>Drop matching entries</description> +    </valueHelp> +    <valueHelp> +      <format>queue</format> +      <description>Enqueue packet to userspace</description> +    </valueHelp> +    <valueHelp> +      <format>offload</format> +      <description>Offload packet via flowtable</description> +    </valueHelp> +    <constraint> +      <regex>(accept|continue|jump|reject|return|drop|queue|offload)</regex> +    </constraint> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i index 7a2eb86d4..e51dd0056 100644 --- a/interface-definitions/include/firewall/common-rule-inet.xml.i +++ b/interface-definitions/include/firewall/common-rule-inet.xml.i @@ -303,6 +303,7 @@    </children>  </node>  #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i>  <node name="time">    <properties>      <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i index a1071a09a..e040c9b13 100644 --- a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i +++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i @@ -260,6 +260,7 @@    </children>  </node>  #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i>  <node name="time">    <properties>      <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 7417a3c58..c62bf2c5f 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -315,6 +315,7 @@    </children>  </node>  #include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i>  <node name="time">    <properties>      <help>Time to match rule</help> diff --git a/interface-definitions/include/firewall/flow-offload.xml.i b/interface-definitions/include/firewall/flow-offload.xml.i deleted file mode 100644 index 706836362..000000000 --- a/interface-definitions/include/firewall/flow-offload.xml.i +++ /dev/null @@ -1,47 +0,0 @@ -<!-- include start from firewall/flow-offload.xml.i --> -<node name="flow-offload"> -  <properties> -    <help>Configurable flow offload options</help> -  </properties> -  <children> -    <leafNode name="disable"> -      <properties> -        <help>Disable flow offload</help> -        <valueless/> -      </properties> -    </leafNode> -    <node name="software"> -      <properties> -        <help>Software offload</help> -      </properties> -      <children> -        <leafNode name="interface"> -          <properties> -            <help>Interfaces to enable</help> -            <completionHelp> -              <script>${vyos_completion_dir}/list_interfaces</script> -            </completionHelp> -            <multi/> -          </properties> -        </leafNode> -      </children> -    </node> -    <node name="hardware"> -      <properties> -        <help>Hardware offload</help> -      </properties> -      <children> -        <leafNode name="interface"> -          <properties> -            <help>Interfaces to enable</help> -            <completionHelp> -              <script>${vyos_completion_dir}/list_interfaces</script> -            </completionHelp> -            <multi/> -          </properties> -        </leafNode> -      </children> -    </node> -  </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index 03c07e657..e655cd6ac 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -271,7 +271,6 @@        </properties>        <defaultValue>disable</defaultValue>      </leafNode> -    #include <include/firewall/flow-offload.xml.i>    </children>  </node>  <!-- include end --> diff --git a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i index 08ee96419..70c0adb77 100644 --- a/interface-definitions/include/firewall/ipv4-hook-forward.xml.i +++ b/interface-definitions/include/firewall/ipv4-hook-forward.xml.i @@ -24,8 +24,10 @@              <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>            </properties>            <children> +            #include <include/firewall/action-forward.xml.i>              #include <include/firewall/common-rule-ipv4.xml.i>              #include <include/firewall/inbound-interface.xml.i> +            #include <include/firewall/offload-target.xml.i>              #include <include/firewall/outbound-interface.xml.i>            </children>          </tagNode> diff --git a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i index 20ab8dbe8..d83827161 100644 --- a/interface-definitions/include/firewall/ipv6-hook-forward.xml.i +++ b/interface-definitions/include/firewall/ipv6-hook-forward.xml.i @@ -24,8 +24,10 @@              <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>            </properties>            <children> +            #include <include/firewall/action-forward.xml.i>              #include <include/firewall/common-rule-ipv6.xml.i>              #include <include/firewall/inbound-interface.xml.i> +            #include <include/firewall/offload-target.xml.i>              #include <include/firewall/outbound-interface.xml.i>            </children>          </tagNode> diff --git a/interface-definitions/include/firewall/offload-target.xml.i b/interface-definitions/include/firewall/offload-target.xml.i new file mode 100644 index 000000000..940ed8091 --- /dev/null +++ b/interface-definitions/include/firewall/offload-target.xml.i @@ -0,0 +1,10 @@ +<!-- include start from firewall/offload-target.xml.i --> +<leafNode name="offload-target"> +  <properties> +    <help>Set flowtable offload target. Action offload must be defined to use this setting</help> +    <completionHelp> +      <path>firewall flowtable</path> +    </completionHelp> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i index e2ce7b9fd..36546c2e4 100644 --- a/interface-definitions/include/firewall/tcp-flags.xml.i +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -1,7 +1,7 @@  <!-- include start from firewall/tcp-flags.xml.i -->  <node name="tcp">    <properties> -    <help>TCP flags to match</help> +    <help>TCP options to match</help>    </properties>    <children>      <node name="flags"> @@ -114,22 +114,6 @@          </node>        </children>      </node> -    <leafNode name="mss"> -      <properties> -        <help>Maximum segment size (MSS)</help> -        <valueHelp> -          <format>u32:1-16384</format> -          <description>Maximum segment size</description> -        </valueHelp> -        <valueHelp> -          <format><min>-<max></format> -          <description>TCP MSS range (use '-' as delimiter)</description> -        </valueHelp> -        <constraint> -          <validator name="numeric" argument="--allow-range --range 1-16384"/> -        </constraint> -      </properties> -    </leafNode>    </children>  </node>  <!-- include end --> diff --git a/interface-definitions/include/firewall/tcp-mss.xml.i b/interface-definitions/include/firewall/tcp-mss.xml.i new file mode 100644 index 000000000..dc49b4272 --- /dev/null +++ b/interface-definitions/include/firewall/tcp-mss.xml.i @@ -0,0 +1,25 @@ +<!-- include start from firewall/tcp-mss.xml.i --> +<node name="tcp"> +  <properties> +    <help>TCP options to match</help> +  </properties> +  <children> +    <leafNode name="mss"> +      <properties> +        <help>Maximum segment size (MSS)</help> +        <valueHelp> +          <format>u32:1-16384</format> +          <description>Maximum segment size</description> +        </valueHelp> +        <valueHelp> +          <format><min>-<max></format> +          <description>TCP MSS range (use '-' as delimiter)</description> +        </valueHelp> +        <constraint> +          <validator name="numeric" argument="--allow-range --range 1-16384"/> +        </constraint> +      </properties> +    </leafNode> +  </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i index 216ec9bea..6551d23ab 100644 --- a/interface-definitions/include/policy/route-common.xml.i +++ b/interface-definitions/include/policy/route-common.xml.i @@ -314,6 +314,7 @@    </children>
  </node>
  #include <include/firewall/tcp-flags.xml.i>
 +#include <include/firewall/tcp-mss.xml.i>
  <node name="time">
    <properties>
      <help>Time to match rule</help>
 diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 3669336fd..5aaa7095c 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -80,6 +80,12 @@                    <valueless/>                  </properties>                </leafNode> +              <leafNode name="hw-tc-offload"> +                <properties> +                  <help>Enable Hardware Flow Offload</help> +                  <valueless/> +                </properties> +              </leafNode>                <leafNode name="lro">                  <properties>                    <help>Enable Large Receive Offload</help> diff --git a/interface-definitions/service-aws-glb.xml.in b/interface-definitions/service-aws-glb.xml.in new file mode 100644 index 000000000..c749fd04e --- /dev/null +++ b/interface-definitions/service-aws-glb.xml.in @@ -0,0 +1,127 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="service"> +    <children> +      <node name="aws"> +        <properties> +          <help>Amazon Web Service</help> +          <priority>1280</priority> +        </properties> +        <children> +          <node name="glb" owner="${vyos_conf_scripts_dir}/service_aws_glb.py"> +            <properties> +              <help>Gateway load-balancer tunnel handler</help> +            </properties> +            <children> +              <node name="script"> +                <properties> +                  <help>Script executed on create or destroy tunnel</help> +                </properties> +                <children> +                  <leafNode name="on-create"> +                    <properties> +                      <help>Script to run when interface is created</help> +                      <constraint> +                        <validator name="script"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="on-destroy"> +                    <properties> +                      <help>Script to run when interface is destroyed</help> +                      <constraint> +                        <validator name="script"/> +                      </constraint> +                    </properties> +                  </leafNode> +                </children> +              </node> +              <node name="status"> +                <properties> +                  <help>Status</help> +                </properties> +                <children> +                  <leafNode name="format"> +                    <properties> +                      <help>Statistic format</help> +                      <completionHelp> +                        <list>simple full</list> +                      </completionHelp> +                      <valueHelp> +                        <format>simple</format> +                        <description>Simple format</description> +                      </valueHelp> +                      <valueHelp> +                        <format>full</format> +                        <description>Full format</description> +                      </valueHelp> +                      <constraint> +                        <regex>(simple|full)</regex> +                      </constraint> +                    </properties> +                  </leafNode> +                  #include <include/port-number.xml.i> +                </children> +              </node> +              <node name="threads"> +                <properties> +                  <help>Threads settings</help> +                </properties> +                <children> +                  <leafNode name="tunnel"> +                    <properties> +                      <help>Number of threads for each tunnel processor</help> +                      <valueHelp> +                        <format>u32:1-256</format> +                        <description>Number of threads</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--range 1-256"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="tunnel-affinity"> +                    <properties> +                      <help>List of cores worker threads</help> +                      <valueHelp> +                        <format><idN>-<idM></format> +                        <description>CPU core id range (use '-' as delimiter)</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--allow-range --range 0-255"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="udp"> +                    <properties> +                      <help>Number of threads for UDP receiver</help> +                      <valueHelp> +                        <format>u32:1-256</format> +                        <description>Number of threads</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--range 1-256"/> +                      </constraint> +                    </properties> +                  </leafNode> +                  <leafNode name="udp-affinity"> +                    <properties> +                      <help>List of cores worker threads</help> +                      <valueHelp> +                        <format><idN>-<idM></format> +                        <description>CPU core id range (use '-' as delimiter)</description> +                      </valueHelp> +                      <constraint> +                        <validator name="numeric" argument="--allow-range --range 0-255"/> +                      </constraint> +                    </properties> +                  </leafNode> +                </children> +              </node> +            </children> +          </node> +        </children> +      </node> +    </children> +  </node> +</interfaceDefinition> diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 78d19090c..4452f1a74 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -127,6 +127,7 @@                            #include <include/nat-port.xml.i>                          </children>                        </node> +                      #include <include/firewall/tcp-flags.xml.i>                      </children>                    </tagNode>                  </children> @@ -212,6 +213,7 @@                            #include <include/nat-port.xml.i>                          </children>                        </node> +                      #include <include/firewall/tcp-flags.xml.i>                      </children>                    </tagNode>                  </children> diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index ca3bcfc3d..f19632719 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -172,6 +172,9 @@ class Ethtool:      def get_generic_segmentation_offload(self):          return self._get_generic('generic-segmentation-offload') +    def get_hw_tc_offload(self): +        return self._get_generic('hw-tc-offload') +      def get_large_receive_offload(self):          return self._get_generic('large-receive-offload') diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 3305eb269..3ca7a25b9 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -249,29 +249,6 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):                      output.append(f'{proto} {prefix}port {operator} @P_{group_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 "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"') -                        ##{family}-{hook}-{fw_name}-{rule_id} -        if 'log_options' in rule_conf: - -            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': '<'}          for op, operator in operators.items(): @@ -393,6 +370,28 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):          if 'priority' in rule_conf['vlan']:              output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') +    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 "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"') +                        ##{family}-{hook}-{fw_name}-{rule_id} +        if 'log_options' in rule_conf: + +            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}')      output.append('counter') @@ -402,20 +401,24 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):      if 'action' in rule_conf:          # 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 rule_conf['action'] == 'offload': +            offload_target = rule_conf['offload_target'] +            output.append(f'flow add @VYOS_FLOWTABLE_{offload_target}') +        else: +            output.append(f'{rule_conf["action"]}') -        if 'queue' in rule_conf['action']: -            if 'queue' in rule_conf: -                target = rule_conf['queue'] -                output.append(f'num {target}') +            if 'jump' in rule_conf['action']: +                target = rule_conf['jump_target'] +                output.append(f'NAME{def_suffix}_{target}') -            if 'queue_options' in rule_conf: -                queue_opts = ','.join(rule_conf['queue_options']) -                output.append(f'{queue_opts}') +            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') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 24ce3a803..285542057 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -57,6 +57,10 @@ class EthernetIf(Interface):              'validate': lambda v: assert_list(v, ['on', 'off']),              'possible': lambda i, v: EthernetIf.feature(i, 'gso', v),          }, +        'hw-tc-offload': { +            'validate': lambda v: assert_list(v, ['on', 'off']), +            'possible': lambda i, v: EthernetIf.feature(i, 'hw-tc-offload', v), +        },          'lro': {              'validate': lambda v: assert_list(v, ['on', 'off']),              'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), @@ -222,6 +226,25 @@ class EthernetIf(Interface):                  print('Adapter does not support changing generic-segmentation-offload settings!')          return False +    def set_hw_tc_offload(self, state): +        """ +        Enable hardware TC flow offload. State can be either True or False. +        Example: +        >>> from vyos.ifconfig import EthernetIf +        >>> i = EthernetIf('eth0') +        >>> i.set_hw_tc_offload(True) +        """ +        if not isinstance(state, bool): +            raise ValueError('Value out of range') + +        enabled, fixed = self.ethtool.get_hw_tc_offload() +        if enabled != state: +            if not fixed: +                return self.set_interface('hw-tc-offload', 'on' if state else 'off') +            else: +                print('Adapter does not support changing hw-tc-offload settings!') +        return False +      def set_lro(self, state):          """          Enable Large Receive offload. State can be either True or False. @@ -358,6 +381,9 @@ class EthernetIf(Interface):          # GSO (generic segmentation offload)          self.set_gso(dict_search('offload.gso', config) != None) +        # GSO (generic segmentation offload) +        self.set_hw_tc_offload(dict_search('offload.hw-tc-offload', config) != None) +          # LRO (large receive offload)          self.set_lro(dict_search('offload.lro', config) != None) diff --git a/python/vyos/template.py b/python/vyos/template.py index add4d3ce5..3be486cc4 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -678,6 +678,11 @@ def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False):          proto = rule_conf['protocol']          output.append(f'meta l4proto {proto}') +    tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags') +    if tcp_flags: +        from vyos.firewall import parse_tcp_flags +        output.append(parse_tcp_flags(tcp_flags)) +      for side in ['source', 'destination']:          if side in rule_conf:              side_conf = rule_conf[side] diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 4c579c760..9354bd495 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -139,7 +139,7 @@ def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool:      import json      from vyos.utils.process import rc_cmd -    rc, out = rc_cmd(f'ip -6 --json address show dev {iface} scope global') +    rc, out = rc_cmd(f'ip -6 --json address show dev {iface}')      if rc:          return False diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 391ef03ff..72e04847a 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -523,8 +523,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.verify_nftables(nftables_search, 'ip vyos_filter')          # Check conntrack -        self.verify_nftables_chain([['accept']], 'raw', 'FW_CONNTRACK') -        self.verify_nftables_chain([['return']], 'ip6 raw', 'FW_CONNTRACK') +        self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') +        self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')      def test_bridge_basic_rules(self):          name = 'smoketest' @@ -603,17 +603,39 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):                  with open(path, 'r') as f:                      self.assertNotEqual(f.read().strip(), conf['default'], msg=path) -    def test_flow_offload_software(self): -        self.cli_set(['firewall', 'global-options', 'flow-offload', 'software', 'interface', 'eth0']) +    def test_flow_offload(self): +        self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) +        self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) + +        # QEMU virtual NIC does not support hw-tc-offload +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() + +        self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) + +        self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload']) +        self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) +        self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) +        self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established', 'enable']) +        self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related', 'enable']) + +        self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload']) +        self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest']) +        self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) +        self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established', 'enable']) +        self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related', 'enable']) +          self.cli_commit() +          nftables_search = [ -            ['flowtable VYOS_FLOWTABLE_software'], -            ['hook ingress priority filter - 1'], +            ['flowtable VYOS_FLOWTABLE_smoketest'], +            ['hook ingress priority filter'],              ['devices = { eth0 }'], -            ['flow add @VYOS_FLOWTABLE_software'], +            ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'],          ] -        self.verify_nftables(nftables_search, 'inet vyos_offload') +        self.verify_nftables(nftables_search, 'ip vyos_filter') +        self.verify_nftables(nftables_search, 'ip6 vyos_filter')  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 118b1d3a2..c7ddf873e 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -250,7 +250,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):              ['meta l4proto udp', 'drop'],              ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],              ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex], -            ['meta l4proto icmp', 'log prefix "[ipv4-route-smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex], +            ['log prefix "[ipv4-route-smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex],              ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex]          ] @@ -262,7 +262,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):              ['meta l4proto udp', 'drop'],              ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex],              ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex], -            ['meta l4proto ipv6-icmp', 'log prefix "[ipv6-route6-smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex], +            ['log prefix "[ipv6-route6-smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex],              ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex]          ] diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index ee8a07b37..357c3dfb1 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -79,8 +79,8 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):              ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}')              # default value 300 seconds              self.assertIn(f'daemon=300', ddclient_conf) -            self.assertIn(f'use=if', ddclient_conf) -            self.assertIn(f'if={interface}', ddclient_conf) +            self.assertIn(f'usev4=ifv4', ddclient_conf) +            self.assertIn(f'ifv4={interface}', ddclient_conf)              self.assertIn(f'password={password}', ddclient_conf)              for opt in details.keys(): diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index ea304783d..c9f184558 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -200,7 +200,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):                      self.assertTrue(os.path.isdir(f'/sys/module/{driver}'))              if 'nftables' in module_options:                  for rule in module_options['nftables']: -                    self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) != None) +                    self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) != None)          # unload modules          for module in modules: @@ -216,7 +216,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):                      self.assertFalse(os.path.isdir(f'/sys/module/{driver}'))              if 'nftables' in module_options:                  for rule in module_options['nftables']: -                    self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) == None) +                    self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) == None)      def test_conntrack_hash_size(self):          hash_size = '65536' @@ -256,6 +256,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):          self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2'])          self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22'])          self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) +        self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'tcp', 'flags', 'syn'])          self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1'])          self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) @@ -274,7 +275,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):          self.cli_commit()          nftables_search = [ -            ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'], +            ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'],              ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack']          ] @@ -284,8 +285,8 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):              ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack']          ] -        self.verify_nftables(nftables_search, 'raw') -        self.verify_nftables(nftables6_search, 'ip6 raw') +        self.verify_nftables(nftables_search, 'ip vyos_conntrack') +        self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack')          self.cli_delete(['firewall']) diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index d55ea616e..6c761579b 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -67,7 +67,7 @@ class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):          self.cli_commit()          # verify configuration -        nftables_output = cmd('sudo nft list chain raw VYOS_CT_PREROUTING_HOOK').splitlines() +        nftables_output = cmd('sudo nft list chain raw VYOS_PREROUTING_HOOK').splitlines()          for interface in Section.interfaces('ethernet'):              rule_found = False              ifname_search = f'iifname "{interface}"' diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index a0de914bc..21a20ea8d 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -20,11 +20,10 @@ import re  from sys import exit  from vyos.config import Config -from vyos.firewall import find_nftables_rule -from vyos.firewall import remove_nftables_rule  from vyos.utils.process import process_named_running  from vyos.utils.dict import dict_search  from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive  from vyos.utils.process import cmd  from vyos.utils.process import rc_cmd  from vyos.utils.process import run @@ -47,8 +46,8 @@ module_map = {          'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],      },      'nfs' : { -        'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return', -                      'ct helper set "rpc_udp" udp dport "{111}" return'] +        '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'], @@ -57,7 +56,7 @@ module_map = {          'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],       },      'sqlnet' : { -        'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return'] +        'nftables' : ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']      },      'tftp' : {          'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'], @@ -87,10 +86,25 @@ def get_config(config=None):                                       get_first_key=True,                                       with_recursive_defaults=True) -    conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), +    conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),                                                   get_first_key=True,                                                   no_tag_node_value_mangle=True) +    conntrack['flowtable_enabled'] = False +    flow_offload = dict_search_args(conntrack['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'): +                conntrack['flowtable_enabled'] = True +                break + +    conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return' +    conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return' +    conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return' +    conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic']) + +    conntrack['module_map'] = module_map +      return conntrack  def verify(conntrack): @@ -104,6 +118,17 @@ def verify(conntrack):                     if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:                         raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}') +                tcp_flags = dict_search_args(rule_config, 'tcp', 'flags') +                if tcp_flags: +                    if dict_search_args(rule_config, 'protocol') != 'tcp': +                        raise ConfigError('Protocol must be tcp when specifying tcp flags') + +                    not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not') +                    if not_flags: +                        duplicates = [flag for flag in tcp_flags if flag in not_flags] +                        if duplicates: +                            raise ConfigError(f'Cannot match a tcp flag as set and not set') +                  for side in ['destination', 'source']:                      if side in rule_config:                          side_conf = rule_config[side] @@ -127,7 +152,7 @@ def verify(conntrack):                                      if inet == 'ipv6':                                          group = f'ipv6_{group}' -                                    group_obj = dict_search_args(conntrack['firewall_group'], group, group_name) +                                    group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name)                                      if group_obj is None:                                          raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule') @@ -138,22 +163,29 @@ def verify(conntrack):      return None  def generate(conntrack): +    if not os.path.exists(nftables_ct_file): +        conntrack['first_install'] = True + +    # Determine if conntrack is needed +    conntrack['ipv4_firewall_action'] = 'return' +    conntrack['ipv6_firewall_action'] = 'return' + +    if conntrack['flowtable_enabled']: +        conntrack['ipv4_firewall_action'] = 'accept' +        conntrack['ipv6_firewall_action'] = 'accept' +    else: +        for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'): +            if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): +                if path[0] == 'ipv4': +                    conntrack['ipv4_firewall_action'] = 'accept' +                elif path[0] == 'ipv6': +                    conntrack['ipv6_firewall_action'] = 'accept' +      render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)      render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)      render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack)      return None -def find_nftables_ct_rule(table, chain, rule): -    helper_search = re.search('ct helper set "(\w+)"', rule) -    if helper_search: -        rule = helper_search[1] -    return find_nftables_rule(table, chain, [rule]) - -def find_remove_rule(table, chain, rule): -    handle = find_nftables_ct_rule(table, chain, rule) -    if handle: -        remove_nftables_rule(table, chain, 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. @@ -164,21 +196,10 @@ def apply(conntrack):                      # Only remove the module if it's loaded                      if os.path.exists(f'/sys/module/{mod}'):                          cmd(f'rmmod {mod}') -            if 'nftables' in module_config: -                for rule in module_config['nftables']: -                    find_remove_rule('raw', 'VYOS_CT_HELPER', rule) -                    find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule)          else:              if 'ko' in module_config:                  for mod in module_config['ko']:                      cmd(f'modprobe {mod}') -            if 'nftables' in module_config: -                for rule in module_config['nftables']: -                    if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule): -                        cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}') - -                    if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule): -                        cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}')      # Load new nftables ruleset      install_result, output = rc_cmd(f'nft -f {nftables_ct_file}') diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index ab80defe8..4b1aed742 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -104,7 +104,7 @@ def generate(dyndns):      if not dyndns or 'address' not in dyndns:          return None -    render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns) +    render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600)      render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns)      return None diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 769cc598f..3d799318e 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -27,6 +27,7 @@ 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.ethtool import Ethtool  from vyos.firewall import fqdn_config_parse  from vyos.firewall import geoip_update  from vyos.template import render @@ -141,13 +142,7 @@ 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 +    set_dependents('conntrack', conf)      return firewall @@ -169,6 +164,15 @@ def verify_rule(firewall, rule_conf, ipv6):              if target not in dict_search_args(firewall, 'ipv6', 'name'):                  raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') +    if rule_conf['action'] == 'offload': +        if 'offload_target' not in rule_conf: +            raise ConfigError('Action set to offload, but no offload-target specified') + +        offload_target = rule_conf['offload_target'] + +        if not dict_search_args(firewall, 'flowtable', offload_target): +            raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system') +      if 'queue_options' in rule_conf:          if 'queue' not in rule_conf['action']:              raise ConfigError('queue-options defined, but action queue needed and it is not defined') @@ -288,7 +292,31 @@ def verify_nested_group(group_name, group, groups, seen):          if 'include' in groups[g]:              verify_nested_group(g, groups[g], groups, seen) +def verify_hardware_offload(ifname): +    ethtool = Ethtool(ifname) +    enabled, fixed = ethtool.get_hw_tc_offload() + +    if not enabled and fixed: +        raise ConfigError(f'Interface "{ifname}" does not support hardware offload') + +    if not enabled: +        raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"') +  def verify(firewall): +    if 'flowtable' in firewall: +        for flowtable, flowtable_conf in firewall['flowtable'].items(): +            if 'interface' not in flowtable_conf: +                raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface') + +            for ifname in flowtable_conf['interface']: +                verify_interface_exists(ifname) + +            if dict_search_args(flowtable_conf, 'offload') == 'hardware': +                interfaces = flowtable_conf['interface'] + +                for ifname in interfaces: +                    verify_hardware_offload(ifname) +      if 'group' in firewall:          for group_type in nested_group_types:              if group_type in firewall['group']: @@ -336,33 +364,12 @@ 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):      if not os.path.exists(nftables_conf):          firewall['first_install'] = True -    # Determine if conntrack is needed -    firewall['ipv4_conntrack_action'] = 'return' -    firewall['ipv6_conntrack_action'] = 'return' -    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 @@ -392,8 +399,7 @@ def apply(firewall):      apply_sysfs(firewall) -    if firewall['group_resync']: -        call_dependents() +    call_dependents()      # T970 Enable a resolver (systemd daemon) that checks      # domain-group/fqdn addresses and update entries for domains by timeout diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 71acd69fa..81ee39df1 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -37,7 +37,7 @@ uacctd_conf_path = '/run/pmacct/uacctd.conf'  systemd_service = 'uacctd.service'  systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf'  nftables_nflog_table = 'raw' -nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK' +nftables_nflog_chain = 'VYOS_PREROUTING_HOOK'  egress_nftables_nflog_table = 'inet mangle'  egress_nftables_nflog_chain = 'FORWARD' diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 70f43ab52..b3b27b14e 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -59,7 +59,7 @@ def get_config(config=None):      if conf.exists(conntrack_path):          ha['conntrack_sync_group'] = conf.return_value(conntrack_path) -    if leaf_node_changed(conf, base + ['vrrp', 'disable-snmp']): +    if leaf_node_changed(conf, base + ['vrrp', 'snmp']):          ha.update({'restart_required': {}})      return ha diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py index ad9c80d72..5da0b906b 100755 --- a/src/conf_mode/load-balancing-wan.py +++ b/src/conf_mode/load-balancing-wan.py @@ -21,6 +21,7 @@ from shutil import rmtree  from vyos.base import Warning  from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents  from vyos.utils.process import cmd  from vyos.template import render  from vyos import ConfigError @@ -49,6 +50,8 @@ def get_config(config=None):          if lb.from_defaults(['rule', rule, 'limit']):              del lb['rule'][rule]['limit'] +    set_dependents('conntrack', conf) +      return lb @@ -132,6 +135,8 @@ def apply(lb):          cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1')          cmd(f'systemctl restart {systemd_service}') +    call_dependents() +      return None diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index e37a7011c..52a7a71fd 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -18,13 +18,12 @@ import jmespath  import json  import os -from distutils.version import LooseVersion -from platform import release as kernel_version  from sys import exit  from netifaces import interfaces  from vyos.base import Warning  from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents  from vyos.template import render  from vyos.template import is_ip_network  from vyos.utils.kernel import check_kmod @@ -38,10 +37,7 @@ from vyos import ConfigError  from vyos import airbag  airbag.enable() -if LooseVersion(kernel_version()) > LooseVersion('5.1'): -    k_mod = ['nft_nat', 'nft_chain_nat'] -else: -    k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] +k_mod = ['nft_nat', 'nft_chain_nat']  nftables_nat_config = '/run/nftables_nat.conf'  nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft' @@ -53,18 +49,27 @@ valid_groups = [      'port_group'  ] -def get_handler(json, chain, target): -    """ Get nftable rule handler number of given chain/target combination. -    Handler is required when adding NAT/Conntrack helper targets """ -    for x in json: -        if x['chain'] != chain: -            continue -        if x['target'] != target: -            continue -        return x['handle'] +def get_config(config=None): +    if config: +        conf = config +    else: +        conf = Config() -    return None +    base = ['nat'] +    nat = conf.get_config_dict(base, key_mangling=('-', '_'), +                               get_first_key=True, +                               with_recursive_defaults=True) +    set_dependents('conntrack', conf) + +    if not conf.exists(base): +        nat['deleted'] = '' +        return nat + +    nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, +                                    no_tag_node_value_mangle=True) + +    return nat  def verify_rule(config, err_msg, groups_dict):      """ Common verify steps used for both source and destination NAT """ @@ -136,62 +141,11 @@ def verify_rule(config, err_msg, groups_dict):              if count != 100:                  Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') -def get_config(config=None): -    if config: -        conf = config -    else: -        conf = Config() - -    base = ['nat'] -    nat = conf.get_config_dict(base, key_mangling=('-', '_'), -                               get_first_key=True, -                               with_recursive_defaults=True) - -    # read in current nftable (once) for further processing -    tmp = cmd('nft -j list table raw') -    nftable_json = json.loads(tmp) - -    # condense the full JSON table into a list with only relevand informations -    pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' -    condensed_json = jmespath.search(pattern, nftable_json) - -    if not conf.exists(base): -        if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'): -            nat['helper_functions'] = 'remove' - -            # Retrieve current table handler positions -            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', 'VYOS_CT_HELPER') -            nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') -        nat['deleted'] = '' -        return nat - -    nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, -                                    no_tag_node_value_mangle=True) - -    # check if NAT connection tracking helpers need to be set up - this has to -    # be done only once -    if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): -        nat['helper_functions'] = 'add' - -        # Retrieve current table handler positions -        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 -  def verify(nat):      if not nat or 'deleted' in nat:          # no need to verify the CLI as NAT is going to be deactivated          return None -    if 'helper_functions' in nat: -        if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']): -            raise Exception('could not determine nftable ruleset handlers') -      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}:' @@ -267,6 +221,8 @@ def apply(nat):          os.unlink(nftables_nat_config)          os.unlink(nftables_static_nat_conf) +    call_dependents() +      return None  if __name__ == '__main__': diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 4c12618bc..46d796bc8 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -23,6 +23,7 @@ from netifaces import interfaces  from vyos.base import Warning  from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents  from vyos.template import render  from vyos.utils.process import cmd  from vyos.utils.kernel import check_kmod @@ -37,18 +38,6 @@ k_mod = ['nft_nat', 'nft_chain_nat']  nftables_nat66_config = '/run/nftables_nat66.nft'  ndppd_config = '/run/ndppd/ndppd.conf' -def get_handler(json, chain, target): -    """ Get nftable rule handler number of given chain/target combination. -    Handler is required when adding NAT66/Conntrack helper targets """ -    for x in json: -        if x['chain'] != chain: -            continue -        if x['target'] != target: -            continue -        return x['handle'] - -    return None -  def get_config(config=None):      if config:          conf = config @@ -58,35 +47,10 @@ def get_config(config=None):      base = ['nat66']      nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) -    # read in current nftable (once) for further processing -    tmp = cmd('nft -j list table ip6 raw') -    nftable_json = json.loads(tmp) - -    # condense the full JSON table into a list with only relevand informations -    pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' -    condensed_json = jmespath.search(pattern, nftable_json) +    set_dependents('conntrack', conf)      if not conf.exists(base): -        nat['helper_functions'] = 'remove' -        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', 'VYOS_CT_HELPER') -        nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')          nat['deleted'] = '' -        return nat - -    # check if NAT66 connection tracking helpers need to be set up - this has to -    # be done only once -    if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): -        nat['helper_functions'] = 'add' - -        # Retrieve current table handler positions -        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'      return nat @@ -95,10 +59,6 @@ def verify(nat):          # no need to verify the CLI as NAT66 is going to be deactivated          return None -    if 'helper_functions' in nat and nat['helper_functions'] != 'has': -        if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']): -            raise Exception('could not determine nftable ruleset handlers') -      if dict_search('source.rule', nat):          for rule, config in dict_search('source.rule', nat).items():              err_msg = f'Source NAT66 configuration error in rule {rule}:' @@ -155,6 +115,8 @@ def apply(nat):      else:          cmd('systemctl restart ndppd') +    call_dependents() +      return None  if __name__ == '__main__': diff --git a/src/conf_mode/service_aws_glb.py b/src/conf_mode/service_aws_glb.py new file mode 100755 index 000000000..d1ed5a07b --- /dev/null +++ b/src/conf_mode/service_aws_glb.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 +# 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/>. + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +systemd_service = 'aws-gwlbtun.service' +systemd_override = '/run/systemd/system/aws-gwlbtun.service.d/10-override.conf' + + +def get_config(config=None): +    if config: +        conf = config +    else: +        conf = Config() +    base = ['service', 'aws', 'glb'] +    if not conf.exists(base): +        return None + +    glb = conf.get_config_dict(base, key_mangling=('-', '_'), +                                      get_first_key=True, +                                      no_tag_node_value_mangle=True) + +    return glb + + +def verify(glb): +    # bail out early - looks like removal from running config +    if not glb: +        return None + + +def generate(glb): +    if not glb: +         return None + +    render(systemd_override, 'aws/override_aws_gwlbtun.conf.j2', glb) + + +def apply(glb): +    call('systemctl daemon-reload') +    if not glb: +        call(f'systemctl stop {systemd_service}') +    else: +        call(f'systemctl restart {systemd_service}') +    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/system-ip.py b/src/conf_mode/system-ip.py index 5e4e5ec28..7612e2c0d 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -20,10 +20,12 @@ from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.configverify import verify_route_map  from vyos.template import render_to_string -from vyos.utils.process import call  from vyos.utils.dict import dict_search  from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_active  from vyos.utils.system import sysctl_write +  from vyos import ConfigError  from vyos import frr  from vyos import airbag @@ -115,16 +117,20 @@ def apply(opt):      value = '48' if (tmp is None) else tmp      sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) -    zebra_daemon = 'zebra' -    # Save original configuration prior to starting any commit actions -    frr_cfg = frr.FRRConfig() - -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in opt: -        frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) +    # During startup of vyos-router that brings up FRR, the service is not yet +    # running when this script is called first. Skip this part and wait for initial +    # commit of the configuration to trigger this statement +    if is_systemd_service_active('frr.service'): +        zebra_daemon = 'zebra' +        # Save original configuration prior to starting any commit actions +        frr_cfg = frr.FRRConfig() + +        # The route-map used for the FIB (zebra) is part of the zebra daemon +        frr_cfg.load_configuration(zebra_daemon) +        frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +        if 'frr_zebra_config' in opt: +            frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) +        frr_cfg.commit_configuration(zebra_daemon)  if __name__ == '__main__':      try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index e40ed38e2..90a1a8087 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -22,8 +22,9 @@ from vyos.configdict import dict_merge  from vyos.configverify import verify_route_map  from vyos.template import render_to_string  from vyos.utils.dict import dict_search -from vyos.utils.system import sysctl_write  from vyos.utils.file import write_file +from vyos.utils.process import is_systemd_service_active +from vyos.utils.system import sysctl_write  from vyos import ConfigError  from vyos import frr  from vyos import airbag @@ -93,16 +94,20 @@ def apply(opt):              if name == 'accept_dad':                  write_file(os.path.join(root, name), value) -    zebra_daemon = 'zebra' -    # Save original configuration prior to starting any commit actions -    frr_cfg = frr.FRRConfig() +    # During startup of vyos-router that brings up FRR, the service is not yet +    # running when this script is called first. Skip this part and wait for initial +    # commit of the configuration to trigger this statement +    if is_systemd_service_active('frr.service'): +        zebra_daemon = 'zebra' +        # Save original configuration prior to starting any commit actions +        frr_cfg = frr.FRRConfig() -    # The route-map used for the FIB (zebra) is part of the zebra daemon -    frr_cfg.load_configuration(zebra_daemon) -    frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') -    if 'frr_zebra_config' in opt: -        frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) -    frr_cfg.commit_configuration(zebra_daemon) +        # The route-map used for the FIB (zebra) is part of the zebra daemon +        frr_cfg.load_configuration(zebra_daemon) +        frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') +        if 'frr_zebra_config' in opt: +            frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) +        frr_cfg.commit_configuration(zebra_daemon)  if __name__ == '__main__':      try: diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py index d8224b3c3..6727b63c2 100755 --- a/src/conf_mode/system_frr.py +++ b/src/conf_mode/system_frr.py @@ -18,7 +18,7 @@ from pathlib import Path  from sys import exit  from vyos import ConfigError -from vyos import airbag +from vyos.base import Warning  from vyos.config import Config  from vyos.logger import syslog  from vyos.template import render_to_string @@ -26,6 +26,8 @@ from vyos.utils.boot import boot_configuration_complete  from vyos.utils.file import read_file  from vyos.utils.file import write_file  from vyos.utils.process import call + +from vyos import airbag  airbag.enable()  # path to daemons config and config status files @@ -62,10 +64,8 @@ def apply(frr_config):      if boot_configuration_complete() and frr_config.get('config_file_changed'):          # Since FRR restart is not safe thing, better to give          # control over this to users -        print(''' -        You need to reboot a router (preferred) or restart FRR -        to apply changes in modules settings -        ''') +        Warning('You need to reboot the router (preferred) or restart '\ +                'FRR to apply changes in modules settings')      # restart FRR automatically      # During initial boot this should be safe in most cases diff --git a/src/systemd/aws-gwlbtun.service b/src/systemd/aws-gwlbtun.service new file mode 100644 index 000000000..97d772dec --- /dev/null +++ b/src/systemd/aws-gwlbtun.service @@ -0,0 +1,11 @@ +[Unit] +Description=Description=AWS Gateway Load Balancer Tunnel Handler +Documentation=https://github.com/aws-samples/aws-gateway-load-balancer-tunnel-handler +After=network.target + +[Service] +ExecStart= +Restart=on-failure + +[Install] +WantedBy=multi-user.target | 
