diff options
89 files changed, 2085 insertions, 1571 deletions
| diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index 2d6f6da41..9500d3aa7 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -8,6 +8,7 @@  "neighbor.py",  "openconnect.py",  "route.py", +"system.py",  "ipsec.py",  "storage.py",  "uptime.py", diff --git a/data/templates/accel-ppp/chap-secrets.ipoe.j2 b/data/templates/accel-ppp/chap-secrets.ipoe.j2 index a1430ec22..43083e22e 100644 --- a/data/templates/accel-ppp/chap-secrets.ipoe.j2 +++ b/data/templates/accel-ppp/chap-secrets.ipoe.j2 @@ -1,18 +1,13 @@  # username  server  password  acceptable local IP addresses   shaper -{% for interface in auth_interfaces %} -{%     for mac in interface.mac %} -{%         if mac.rate_upload and mac.rate_download %} -{%             if mac.vlan_id %} -{{ interface.name }}.{{ mac.vlan_id }} * {{ mac.address | lower }} * {{ mac.rate_download }}/{{ mac.rate_upload }} -{%             else %} -{{ interface.name }} * {{ mac.address | lower }}  * {{ mac.rate_download }}/{{ mac.rate_upload }} -{%             endif %} -{%         else %} -{%             if mac.vlan_id %} -{{ interface.name }}.{{ mac.vlan_id }} * {{ mac.address | lower }} * -{%             else %} -{{ interface.name }} * {{ mac.address | lower }}  * -{%             endif %} +{% if authentication.interface is vyos_defined %} +{%     for iface, iface_config in authentication.interface.items() %} +{%         if iface_config.mac is vyos_defined %} +{%             for mac, mac_config in iface_config.mac.items() %} +{%                 if mac_config.vlan is vyos_defined %} +{%                     set iface = iface ~ '.' ~ mac_config.vlan %} +{%                 endif %} +{{ "%-11s" | format(iface) }} * {{ mac | lower }} * {{ mac_config.rate_limit.download ~ '/' ~ mac_config.rate_limit.upload if mac_config.rate_limit.download is vyos_defined and mac_config.rate_limit.upload is vyos_defined }} +{%             endfor %}  {%         endif %}  {%     endfor %} -{% endfor %} +{% endif %} diff --git a/data/templates/accel-ppp/config_ipv6_pool.j2 b/data/templates/accel-ppp/config_ipv6_pool.j2 index 953469577..a1562a1eb 100644 --- a/data/templates/accel-ppp/config_ipv6_pool.j2 +++ b/data/templates/accel-ppp/config_ipv6_pool.j2 @@ -1,6 +1,7 @@  {% if client_ipv6_pool is vyos_defined %}  [ipv6-nd]  AdvAutonomousFlag=1 +verbose=1  {%     if client_ipv6_pool.prefix is vyos_defined %}  [ipv6-pool] @@ -13,6 +14,7 @@ delegate={{ prefix }},{{ options.delegation_prefix }}  {%             endfor %}  {%         endif %}  {%     endif %} +  {%     if client_ipv6_pool.delegate is vyos_defined %}  [ipv6-dhcp]  verbose=1 diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2 index 6df12db2c..99227ea33 100644 --- a/data/templates/accel-ppp/ipoe.config.j2 +++ b/data/templates/accel-ppp/ipoe.config.j2 @@ -4,18 +4,15 @@  log_syslog  ipoe  shaper +{# Common authentication backend definitions #} +{% include 'accel-ppp/config_modules_auth_mode.j2' %}  ipv6pool  ipv6_nd  ipv6_dhcp  ippool -{% if auth_mode == 'radius' %} -radius -{% elif auth_mode == 'local' %} -chap-secrets -{% endif %}  [core] -thread-count={{ thread_cnt }} +thread-count={{ thread_count }}  [log]  syslog=accel-ipoe,daemon @@ -24,28 +21,34 @@ level=5  [ipoe]  verbose=1 -{% for interface in interfaces %} -{%     set tmp = 'interface=' %} -{%     if interface.vlan_mon %} -{%         set tmp = tmp ~ 're:' ~ interface.name ~ '\.\d+' %} -{%     else %} -{%         set tmp = tmp ~ interface.name %} -{%     endif %} -{{ tmp }},shared={{ interface.shared }},mode={{ interface.mode }},ifcfg={{ interface.ifcfg }}{{ ',range=' ~ interface.range if interface.range is defined and interface.range is not none }},start={{ interface.sess_start }},ipv6=1 -{% endfor %} -{% if auth_mode == 'noauth' %} +{% if interface is vyos_defined %} +{%     for iface, iface_config in interface.items() %} +{%         set tmp = 'interface=' %} +{%         if iface_config.vlan is vyos_defined %} +{%             set tmp = tmp ~ 're:' ~ iface ~ '\.\d+' %} +{%         else %} +{%             set tmp = tmp ~ iface %} +{%         endif %} +{%         set shared = '' %} +{%         if iface_config.network is vyos_defined('shared') %} +{%             set shared = 'shared=1,' %} +{%         elif iface_config.network is vyos_defined('vlan') %} +{%             set shared = 'shared=0,' %} +{%         endif %} +{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,range={{ iface_config.client_subnet }},start=dhcpv4,ipv6=1 +{%     endfor %} +{% endif %} +{% if authentication.mode is vyos_defined('noauth') %}  noauth=1 -{%     if client_named_ip_pool %} -{%         for pool in client_named_ip_pool %} -{%             if pool.subnet is defined  %} -ip-pool={{ pool.name }} -{%             endif %} -{%             if pool.gateway_address is defined %} -gw-ip-address={{ pool.gateway_address }}/{{ pool.subnet.split('/')[1] }} +{%     if client_ip_pool.name is vyos_defined %} +{%         for pool, pool_options in client_ip_pool.name.items() %} +{%             if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %} +ip-pool={{ pool }} +gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}  {%             endif %}  {%         endfor %}  {%     endif %} -{% elif auth_mode == 'local' %} +{% elif authentication.mode is vyos_defined('local') %}  username=ifname  password=csid  {% endif %} @@ -57,92 +60,27 @@ vlan-mon={{ interface.name }},{{ interface.vlan_mon | join(',') }}  {%     endif %}  {% endfor %} -{% if dnsv4 %} -[dns] -{%     for dns in dnsv4 %} -dns{{ loop.index }}={{ dns }} -{%     endfor %} -{% endif %} - -{% if dnsv6 %} -[ipv6-dns] -{%     for dns in dnsv6 %} -{{ dns }} -{%     endfor %} -{% endif %} - -[ipv6-nd] -verbose=1 - -[ipv6-dhcp] -verbose=1 - -{% if client_named_ip_pool %} +{% if client_ip_pool.name is vyos_defined %}  [ip-pool] -{%     for pool in client_named_ip_pool %} -{%         if pool.subnet is defined  %} -{{ pool.subnet }},name={{ pool.name }} -{%         endif %} -{%         if pool.gateway_address is defined %} -gw-ip-address={{ pool.gateway_address }}/{{ pool.subnet.split('/')[1] }} +{%     for pool, pool_options in client_ip_pool.name.items() %} +{%         if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %} +{{ pool_options.subnet }},name={{ pool }} +gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}  {%         endif %}  {%     endfor %}  {% endif %} -{% if client_ipv6_pool %} -[ipv6-pool] -{%     for p in client_ipv6_pool %} -{{ p.prefix }},{{ p.mask }} -{%     endfor %} -{%     for p in client_ipv6_delegate_prefix %} -delegate={{ p.prefix }},{{ p.mask }} -{%     endfor %} -{% endif %} +{# Common IPv6 pool definitions #} +{% include 'accel-ppp/config_ipv6_pool.j2' %} -{% if auth_mode == 'local' %} -[chap-secrets] -chap-secrets={{ chap_secrets_file }} -{% elif auth_mode == 'radius' %} -[radius] -verbose=1 -{%     for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} -{%     endfor %} - -{%     if radius_acct_inter_jitter %} -acct-interim-jitter={{ radius_acct_inter_jitter }} -{%     endif %} +{# Common DNS name-server definition #} +{% include 'accel-ppp/config_name_server.j2' %} -acct-timeout={{ radius_acct_tmo }} -timeout={{ radius_timeout }} -max-try={{ radius_max_try }} -{%     if radius_nas_id %} -nas-identifier={{ radius_nas_id }} -{%     endif %} -{%     if radius_nas_ip %} -nas-ip-address={{ radius_nas_ip }} -{%     endif %} -{%     if radius_source_address %} -bind={{ radius_source_address }} -{%     endif %} -{%     if radius_dynamic_author %} -dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }} -{%     endif %} +{# Common chap-secrets and RADIUS server/option definitions #} +{% include 'accel-ppp/config_chap_secrets_radius.j2' %} -{%     if radius_shaper_enable %} -[shaper] -verbose=1 -{%         if radius_shaper_attr %} -attr={{ radius_shaper_attr }} -{%         endif %} -{%         if radius_shaper_multiplier %} -rate-multiplier={{ radius_shaper_multiplier }} -{%         endif %} -{%         if radius_shaper_vendor %} -vendor={{ radius_shaper_vendor }} -{%         endif %} -{%     endif %} -{% endif %} +{# Common RADIUS shaper configuration #} +{% include 'accel-ppp/config_shaper_radius.j2' %}  [cli]  tcp=127.0.0.1:2002 diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2 index 0a92e2d54..f4129d3e2 100644 --- a/data/templates/accel-ppp/pppoe.config.j2 +++ b/data/templates/accel-ppp/pppoe.config.j2 @@ -105,20 +105,13 @@ ac-name={{ access_concentrator }}  {% if interface is vyos_defined %}  {%     for iface, iface_config in interface.items() %} -{%         if iface_config.vlan_id is not vyos_defined and iface_config.vlan_range is not vyos_defined %} +{%         if iface_config.vlan is not vyos_defined %}  interface={{ iface }} -{%         endif %} -{%         if iface_config.vlan_range is vyos_defined %} -{%             for regex in iface_config.regex %} -interface=re:^{{ iface | replace('.', '\\.') }}\.({{ regex }})$ -{%             endfor %} -vlan-mon={{ iface }},{{ iface_config.vlan_range | join(',') }} -{%         endif %} -{%         if iface_config.vlan_id is vyos_defined %} -{%             for vlan in iface_config.vlan_id %} -vlan-mon={{ iface }},{{ vlan }} -interface=re:^{{ iface | replace('.', '\\.') }}\.{{ vlan }}$ +{%         else %} +{%             for vlan in iface_config.vlan %} +interface=re:^{{ iface }}\.{{ vlan | range_to_regex }}$  {%             endfor %} +vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}  {%         endif %}  {%     endfor %}  {% endif %} diff --git a/data/templates/dhcp-client/ipv6.j2 b/data/templates/dhcp-client/ipv6.j2 index e136b1789..b5e55cdd1 100644 --- a/data/templates/dhcp-client/ipv6.j2 +++ b/data/templates/dhcp-client/ipv6.j2 @@ -40,20 +40,22 @@ id-assoc pd {{ pd }} {      prefix ::/{{ pd_config.length }} infinity;  {%         set sla_len = 64 - pd_config.length | int %}  {%         set count = namespace(value=0) %} -{%         for interface, interface_config in pd_config.interface.items() if pd_config.interface is vyos_defined %} +{%         if pd_config.interface is vyos_defined %} +{%             for interface, interface_config in pd_config.interface.items() if pd_config.interface is vyos_defined %}      prefix-interface {{ interface }} {          sla-len {{ sla_len }}; -{%             if interface_config.sla_id is vyos_defined %} +{%                 if interface_config.sla_id is vyos_defined %}          sla-id {{ interface_config.sla_id }}; -{%             else %} +{%                 else %}          sla-id {{ count.value }}; -{%             endif %} -{%             if interface_config.address is vyos_defined %} +{%                 endif %} +{%                 if interface_config.address is vyos_defined %}          ifid {{ interface_config.address }}; -{%             endif %} +{%                 endif %}      }; -{%             set count.value = count.value + 1 %} -{%         endfor %} +{%                 set count.value = count.value + 1 %} +{%             endfor %} +{%         endif %}  };  {%     endfor %}  {% endif %} diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index 97fc123d5..5336f7ee6 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -7,6 +7,7 @@      set A_{{ group_name }} {          type {{ ip_type }}          flags interval +        auto-merge  {%             if group_conf.address is vyos_defined or includes %}          elements = { {{ group_conf.address | nft_nested_group(includes, group.address_group, 'address') | join(",") }} }  {%             endif %} @@ -19,6 +20,7 @@      set A6_{{ group_name }} {          type {{ ip_type }}          flags interval +        auto-merge  {%             if group_conf.address is vyos_defined or includes %}          elements = { {{ group_conf.address | nft_nested_group(includes, group.ipv6_address_group, 'address') | join(",") }} }  {%             endif %} @@ -42,6 +44,7 @@      set N_{{ group_name }} {          type {{ ip_type }}          flags interval +        auto-merge  {%             if group_conf.network is vyos_defined or includes %}          elements = { {{ group_conf.network | nft_nested_group(includes, group.network_group, 'network') | join(",") }} }  {%             endif %} @@ -54,6 +57,7 @@      set N6_{{ group_name }} {          type {{ ip_type }}          flags interval +        auto-merge  {%             if group_conf.network is vyos_defined or includes %}          elements = { {{ group_conf.network | nft_nested_group(includes, group.ipv6_network_group, 'network') | join(",") }} }  {%             endif %} @@ -66,6 +70,7 @@      set P_{{ group_name }} {          type inet_service          flags interval +        auto-merge  {%             if group_conf.port is vyos_defined or includes %}          elements = { {{ group_conf.port | nft_nested_group(includes, group.port_group, 'port') | join(",") }} }  {%             endif %} diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2 index 1481e9104..55fe6024b 100644 --- a/data/templates/firewall/nftables-nat.j2 +++ b/data/templates/firewall/nftables-nat.j2 @@ -1,146 +1,5 @@  #!/usr/sbin/nft -f -{% macro nat_rule(rule, config, chain) %} -{% set comment  = '' %} -{% set base_log = '' %} -{% set src_addr  = 'ip saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %} -{% set dst_addr  = 'ip daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %} -{# negated port groups need special treatment, move != in front of { } group #} -{% if config.source.port is vyos_defined and config.source.port.startswith('!') %} -{%     set src_port  = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %} -{% else %} -{%     set src_port  = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %} -{% endif %} -{# negated port groups need special treatment, move != in front of { } group #} -{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %} -{%     set dst_port  = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %} -{% else %} -{%     set dst_port  = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %} -{% endif %} -{% if chain is vyos_defined('PREROUTING') %} -{%     set comment   = 'DST-NAT-' ~ rule %} -{%     set base_log  = '[NAT-DST-' ~ rule %} -{%     set interface = ' iifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %} -{%     if config.translation.address is vyos_defined %} -{#         support 1:1 network translation #} -{%         if config.translation.address | is_ip_network %} -{%             set trns_addr = 'dnat ip prefix to ip daddr map { ' ~ config.destination.address ~ ' : ' ~ config.translation.address ~ ' }' %} -{#             we can now clear out the dst_addr part as it's already covered in aboves map #} -{%             set dst_addr  = '' %} -{%         else %} -{%             set trns_addr = 'dnat to ' ~ config.translation.address %} -{%         endif %} -{%     endif %} -{% elif chain is vyos_defined('POSTROUTING') %} -{%     set comment   = 'SRC-NAT-' ~ rule %} -{%     set base_log  = '[NAT-SRC-' ~ rule %} -{%     set interface = ' oifname "' ~ config.outbound_interface ~ '"' if config.outbound_interface is vyos_defined and config.outbound_interface is not vyos_defined('any') else '' %} -{%     if config.translation.address is vyos_defined %} -{%         if config.translation.address is vyos_defined('masquerade') %} -{%             set trns_addr = config.translation.address %} -{%             if config.translation.port is vyos_defined %} -{%                 set trns_addr = trns_addr ~ ' to ' %} -{%             endif %} -{#         support 1:1 network translation #} -{%         elif config.translation.address | is_ip_network %} -{%             set trns_addr = 'snat ip prefix to ip saddr map { ' ~ config.source.address ~ ' : ' ~ config.translation.address ~ ' }' %} -{#             we can now clear out the src_addr part as it's already covered in aboves map #} -{%             set src_addr  = '' %} -{%         else %} -{%             set trns_addr = 'snat to ' ~ config.translation.address %} -{%         endif %} -{%     endif %} -{% endif %} -{% set trns_port = ':' ~ config.translation.port if config.translation.port is vyos_defined %} -{# protocol has a default value thus it is always present #} -{% if config.protocol is vyos_defined('tcp_udp') %} -{%     set protocol  = 'tcp' %} -{%     set comment   = comment ~ ' tcp_udp' %} -{% else %} -{%     set protocol  = config.protocol %} -{% endif %} -{% if config.log is vyos_defined %} -{%     if config.exclude is vyos_defined %} -{%         set log = base_log ~ '-EXCL]' %} -{%     elif config.translation.address is vyos_defined('masquerade') %} -{%         set log = base_log ~ '-MASQ]' %} -{%     else %} -{%         set log = base_log ~ ']' %} -{%     endif %} -{% endif %} -{% if config.exclude is vyos_defined %} -{#     rule has been marked as 'exclude' thus we simply return here #} -{%     set trns_addr = 'return' %} -{%     set trns_port = '' %} -{% endif %} -{# T1083: NAT address and port translation options #} -{% if config.translation.options is vyos_defined %} -{%     if config.translation.options.address_mapping is vyos_defined('persistent') %} -{%         set trns_opts_addr  = 'persistent' %} -{%     endif %} -{%     if config.translation.options.port_mapping is vyos_defined('random') %} -{%         set trns_opts_port  = 'random' %} -{%     elif config.translation.options.port_mapping is vyos_defined('fully-random') %} -{%         set trns_opts_port  = 'fully-random' %} -{%     endif %} -{% endif %} -{% if trns_opts_addr is vyos_defined and trns_opts_port is vyos_defined %} -{%     set trns_opts  = trns_opts_addr ~ ',' ~ trns_opts_port %} -{% elif trns_opts_addr is vyos_defined %} -{%     set trns_opts  = trns_opts_addr %} -{% elif trns_opts_port is vyos_defined %} -{%     set trns_opts  = trns_opts_port %} -{% endif %} -{% set output = 'add rule ip nat ' ~ chain ~ interface %} -{% if protocol is not vyos_defined('all') %} -{%     set output = output ~ ' ip protocol ' ~ protocol %} -{% endif %} -{% if src_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ src_addr %} -{% endif %} -{% if src_port is vyos_defined %} -{%     set output = output ~ ' ' ~ protocol ~ ' ' ~ src_port %} -{% endif %} -{% if dst_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ dst_addr %} -{% endif %} -{% if dst_port is vyos_defined %} -{%     set output = output ~ ' ' ~ protocol ~ ' ' ~ dst_port %} -{% endif %} -{# Count packets #} -{% set output = output ~ ' counter' %} -{# Special handling of log option, we must repeat the entire rule before the #} -{# NAT translation options are added, this is essential                      #} -{% if log is vyos_defined %} -{%     set log_output = output ~ ' log prefix "' ~ log ~ '" comment "' ~ comment ~ '"' %} -{% endif %} -{% if trns_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ trns_addr %} -{% endif %} -{% if trns_port is vyos_defined %} -{#     Do not add a whitespace here, translation port must be directly added after IP address #} -{#     e.g. 192.0.2.10:3389                                                                   #} -{%     set output = output ~ trns_port %} -{% endif %} -{% if trns_opts is vyos_defined %} -{%     set output = output ~ ' ' ~ trns_opts %} -{% endif %} -{% if comment is vyos_defined %} -{%     set output = output ~ ' comment "' ~ comment ~ '"' %} -{% endif %} -{{ log_output if log_output is vyos_defined }} -{{ output }} -{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} -{% if config.protocol is vyos_defined('tcp_udp') %} -{#     Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} -{{ log_output | replace('tcp ', 'udp ') if log_output is vyos_defined }} -{{ output | replace('tcp ', 'udp ') }} -{% endif %} -{% endmacro %} - -# Start with clean SNAT and DNAT chains -flush chain ip nat PREROUTING -flush chain ip nat POSTROUTING  {% 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' %} @@ -162,21 +21,41 @@ add rule ip raw NAT_CONNTRACK counter accept  {{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK  {% endif %} -# -# Destination NAT rules build up here -# -add rule ip nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK +{% if first_install is not vyos_defined %} +delete table ip vyos_nat +{% endif %} +table ip vyos_nat { +    # +    # Destination NAT rules build up here +    # +    chain PREROUTING { +        type nat hook prerouting priority -100; policy accept; +        counter jump VYOS_PRE_DNAT_HOOK  {% if destination.rule is vyos_defined %}  {%     for rule, config in destination.rule.items() if config.disable is not vyos_defined %} -{{ nat_rule(rule, config, 'PREROUTING') }} +        {{ config | nat_rule(rule, 'destination') }}  {%     endfor %}  {% endif %} -# -# Source NAT rules build up here -# -add rule ip nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK +    } + +    # +    # Source NAT rules build up here +    # +    chain POSTROUTING { +        type nat hook postrouting priority 100; policy accept; +        counter jump VYOS_PRE_SNAT_HOOK  {% if source.rule is vyos_defined %}  {%     for rule, config in source.rule.items() if config.disable is not vyos_defined %} -{{ nat_rule(rule, config, 'POSTROUTING') }} +        {{ config | nat_rule(rule, 'source') }}  {%     endfor %}  {% endif %} +    } + +    chain VYOS_PRE_DNAT_HOOK { +        return +    } + +    chain VYOS_PRE_SNAT_HOOK { +        return +    } +} diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2 index 28714c7a7..27b3eec88 100644 --- a/data/templates/firewall/nftables-nat66.j2 +++ b/data/templates/firewall/nftables-nat66.j2 @@ -1,125 +1,5 @@  #!/usr/sbin/nft -f -{% macro nptv6_rule(rule,config, chain) %} -{% set comment  = '' %} -{% set base_log = '' %} -{% set dst_prefix  = 'ip6 daddr ' ~ config.destination.prefix.replace('!','!= ') if config.destination.prefix is vyos_defined %} -{% set src_prefix  = 'ip6 saddr ' ~ config.source.prefix.replace('!','!= ') if config.source.prefix is vyos_defined %} -{% set source_address  = 'ip6 saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %} -{% set dest_address  = 'ip6 daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %} -{# Port #} -{% if config.source.port is vyos_defined and config.source.port.startswith('!') %} -{%     set src_port  = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %} -{% else %} -{%     set src_port  = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %} -{% endif %} -{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %} -{%     set dst_port  = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %} -{% else %} -{%     set dst_port  = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %} -{% endif %} -{% if chain is vyos_defined('PREROUTING') %} -{%     set comment   = 'DST-NAT66-' ~ rule %} -{%     set base_log  = '[NAT66-DST-' ~ rule %} -{%     set interface = ' iifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %} -{%     if config.translation.address | is_ip_network %} -{#         support 1:1 network translation #} -{%         set dnat_type = 'dnat prefix to ' %} -{%     else   %} -{%         set dnat_type = 'dnat to ' %} -{%     endif %} -{%     set trns_address = dnat_type ~ config.translation.address if config.translation.address is vyos_defined %} -{% elif chain is vyos_defined('POSTROUTING') %} -{%     set comment   = 'SRC-NAT66-' ~ rule %} -{%     set base_log  = '[NAT66-SRC-' ~ rule %} -{%     if config.translation.address is vyos_defined %} -{%         if config.translation.address is vyos_defined('masquerade') %} -{%             set trns_address = config.translation.address %} -{%         else %} -{%             if config.translation.address | is_ip_network %} -{#                 support 1:1 network translation #} -{%                 set snat_type = 'snat prefix to ' %} -{%             else   %} -{%                 set snat_type = 'snat to ' %} -{%             endif %} -{%             set trns_address = snat_type ~ config.translation.address %} -{%         endif %} -{%     endif   %} -{%     set interface = ' oifname "' ~ config.outbound_interface ~ '"' if config.outbound_interface is vyos_defined else '' %} -{% endif %} -{% set trns_port = ':' ~ config.translation.port if config.translation.port is vyos_defined %} -{# protocol has a default value thus it is always present #} -{% if config.protocol is vyos_defined('tcp_udp') %} -{%     set protocol  = 'tcp' %} -{%     set comment   = comment ~ ' tcp_udp' %} -{% else %} -{%     set protocol  = config.protocol %} -{% endif %} -{% if config.log is vyos_defined %} -{%     if config.translation.address is vyos_defined('masquerade') %} -{%         set log = base_log ~ '-MASQ]' %} -{%     else %} -{%         set log = base_log ~ ']' %} -{%     endif %} -{% endif %} -{% if config.exclude is vyos_defined %} -{#     rule has been marked as 'exclude' thus we simply return here #} -{%     set trns_addr = 'return' %} -{%     set trns_port = '' %} -{% endif %} -{% set output = 'add rule ip6 nat ' ~ chain ~ interface %} -{# Count packets #} -{% set output = output ~ ' counter' %} -{# Special handling of log option, we must repeat the entire rule before the #} -{# NAT translation options are added, this is essential                      #} -{% if log is vyos_defined %} -{%     set log_output = output ~ ' log prefix "' ~ log ~ '" comment "' ~ comment ~ '"' %} -{% endif %} -{% if src_prefix is vyos_defined %} -{%     set output = output ~ ' ' ~ src_prefix %} -{% endif %} -{% if dst_port is vyos_defined %} -{%     set output = output ~ ' ' ~ protocol ~ ' ' ~ dst_port %} -{% endif %} -{% if dst_prefix is vyos_defined %} -{%     set output = output ~ ' ' ~ dst_prefix %} -{% endif %} -{% if source_address is vyos_defined %} -{%     set output = output ~ ' ' ~ source_address %} -{% endif %} -{% if src_port is vyos_defined %} -{%     set output = output ~ ' ' ~ protocol ~ ' ' ~ src_port %} -{% endif %} -{% if dest_address is vyos_defined %} -{%     set output = output ~ ' ' ~ dest_address %} -{% endif %} -{% if config.exclude is vyos_defined %} -{#     rule has been marked as 'exclude' thus we simply return here #} -{%     set trns_address = 'return' %} -{% endif %} -{% if trns_address is vyos_defined %} -{%     set output = output ~ ' ' ~ trns_address %} -{% endif %} -{% if trns_port is vyos_defined %} -{#     Do not add a whitespace here, translation port must be directly added after IP address #} -{#     e.g. 2001:db8::1:3389                                                                   #} -{%     set output = output ~ trns_port %} -{% endif %} -{% if comment is vyos_defined %} -{%     set output = output ~ ' comment "' ~ comment ~ '"' %} -{% endif %} -{{ log_output if log_output is vyos_defined }} -{{ output }} -{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #} -{% if config.protocol is vyos_defined('tcp_udp') %} -{#     Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #} -{{ log_output | replace('tcp ', 'udp ') if log_output is vyos_defined }} -{{ output | replace('tcp ', 'udp ') }} -{% endif %} -{% endmacro %} - -# Start with clean NAT table -flush table ip6 nat  {% 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' %} @@ -137,19 +17,41 @@ add rule ip6 raw NAT_CONNTRACK counter accept  {{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK  {% endif %} -# -# Destination NAT66 rules build up here -# +{% if first_install is not vyos_defined %} +delete table ip6 vyos_nat +{% endif %} +table ip6 vyos_nat { +    # +    # Destination NAT66 rules build up here +    # +    chain PREROUTING { +        type nat hook prerouting priority -100; policy accept; +        counter jump VYOS_DNPT_HOOK  {% if destination.rule is vyos_defined %}  {%     for rule, config in destination.rule.items() if config.disable is not vyos_defined %} -{{ nptv6_rule(rule, config, 'PREROUTING') }} +        {{ config | nat_rule(rule, 'destination', ipv6=True) }}  {%     endfor %}  {% endif %} -# -# Source NAT66 rules build up here -# +    } + +    # +    # Source NAT66 rules build up here +    # +    chain POSTROUTING { +        type nat hook postrouting priority 100; policy accept; +        counter jump VYOS_SNPT_HOOK  {% if source.rule is vyos_defined %}  {%     for rule, config in source.rule.items() if config.disable is not vyos_defined %} -{{ nptv6_rule(rule, config, 'POSTROUTING') }} +        {{ config | nat_rule(rule, 'source', ipv6=True) }}  {%     endfor %}  {% endif %} +    } + +    chain VYOS_DNPT_HOOK { +        return +    } + +    chain VYOS_SNPT_HOOK { +        return +    } +} diff --git a/data/templates/firewall/nftables-static-nat.j2 b/data/templates/firewall/nftables-static-nat.j2 index d3c43858f..790c33ce9 100644 --- a/data/templates/firewall/nftables-static-nat.j2 +++ b/data/templates/firewall/nftables-static-nat.j2 @@ -1,115 +1,31 @@  #!/usr/sbin/nft -f -{% macro nat_rule(rule, config, chain) %} -{% set comment  = '' %} -{% set base_log = '' %} - -{% if chain is vyos_defined('PREROUTING') %} -{%     set comment   = 'STATIC-NAT-' ~ rule %} -{%     set base_log  = '[NAT-DST-' ~ rule %} -{%     set interface = ' iifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %} -{%     if config.translation.address is vyos_defined %} -{#         support 1:1 network translation #} -{%         if config.translation.address | is_ip_network %} -{%             set trns_addr = 'dnat ip prefix to ip daddr map { ' ~ config.destination.address ~ ' : ' ~ config.translation.address ~ ' }' %} -{#             we can now clear out the dst_addr part as it's already covered in aboves map #} -{%         else %} -{%             set dst_addr  = 'ip daddr ' ~ config.destination.address if config.destination.address is vyos_defined %} -{%             set trns_addr = 'dnat to ' ~ config.translation.address %} -{%         endif %} -{%     endif %} -{% elif chain is vyos_defined('POSTROUTING') %} -{%     set comment   = 'STATIC-NAT-' ~ rule %} -{%     set base_log  = '[NAT-SRC-' ~ rule %} -{%     set interface = ' oifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %} -{%     if config.translation.address is vyos_defined %} -{#         support 1:1 network translation #} -{%         if config.translation.address | is_ip_network %} -{%             set trns_addr = 'snat ip prefix to ip saddr map { ' ~ config.translation.address ~ ' : ' ~ config.destination.address ~ ' }' %} -{#             we can now clear out the src_addr part as it's already covered in aboves map #} -{%         else %} -{%             set src_addr  = 'ip saddr ' ~ config.translation.address if config.translation.address is vyos_defined %} -{%             set trns_addr = 'snat to ' ~ config.destination.address %} -{%         endif %} -{%     endif %} -{% endif %} - -{% if config.exclude is vyos_defined %} -{#     rule has been marked as 'exclude' thus we simply return here #} -{%     set trns_addr = 'return' %} -{%     set trns_port = '' %} -{% endif %} - -{% if config.translation.options is vyos_defined %} -{%     if config.translation.options.address_mapping is vyos_defined('persistent') %} -{%         set trns_opts_addr  = 'persistent' %} -{%     endif %} -{%     if config.translation.options.port_mapping is vyos_defined('random') %} -{%         set trns_opts_port  = 'random' %} -{%     elif config.translation.options.port_mapping is vyos_defined('fully-random') %} -{%         set trns_opts_port  = 'fully-random' %} -{%     endif %} -{% endif %} - -{% if trns_opts_addr is vyos_defined and trns_opts_port is vyos_defined %} -{%     set trns_opts  = trns_opts_addr ~ ',' ~ trns_opts_port %} -{% elif trns_opts_addr is vyos_defined %} -{%     set trns_opts  = trns_opts_addr %} -{% elif trns_opts_port is vyos_defined %} -{%     set trns_opts  = trns_opts_port %} -{% endif %} - -{% set output = 'add rule ip vyos_static_nat ' ~ chain ~ interface %} - -{% if dst_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ dst_addr %} +{% if first_install is not vyos_defined %} +delete table ip vyos_static_nat  {% endif %} -{% if src_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ src_addr %} -{% endif %} - -{# Count packets #} -{% set output = output ~ ' counter' %} -{# Special handling of log option, we must repeat the entire rule before the #} -{# NAT translation options are added, this is essential                      #} -{% if log is vyos_defined %} -{%     set log_output = output ~ ' log prefix "' ~ log ~ '" comment "' ~ comment ~ '"' %} -{% endif %} -{% if trns_addr is vyos_defined %} -{%     set output = output ~ ' ' ~ trns_addr %} -{% endif %} - -{% if trns_opts is vyos_defined %} -{%     set output = output ~ ' ' ~ trns_opts %} -{% endif %} -{% if comment is vyos_defined %} -{%     set output = output ~ ' comment "' ~ comment ~ '"' %} -{% endif %} -{{ log_output if log_output is vyos_defined }} -{{ output }} -{% endmacro %} - -# Start with clean STATIC NAT chains -flush chain ip vyos_static_nat PREROUTING -flush chain ip vyos_static_nat POSTROUTING +table ip vyos_static_nat { +    # +    # Destination NAT rules build up here +    # -{# NAT if enabled - add targets to nftables #} - -# -# Destination NAT rules build up here -# -add rule ip vyos_static_nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK +    chain PREROUTING { +        type nat hook prerouting priority -100; policy accept;  {% if static.rule is vyos_defined %}  {%     for rule, config in static.rule.items() if config.disable is not vyos_defined %} -{{ nat_rule(rule, config, 'PREROUTING') }} +    {{ config | nat_static_rule(rule, 'destination') }}  {%     endfor %}  {% endif %} -# -# Source NAT rules build up here -# -add rule ip vyos_static_nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK +    } + +    # +    # Source NAT rules build up here +    # +    chain POSTROUTING { +        type nat hook postrouting priority 100; policy accept;  {% if static.rule is vyos_defined %}  {%     for rule, config in static.rule.items() if config.disable is not vyos_defined %} -{{ nat_rule(rule, config, 'POSTROUTING') }} +    {{ config | nat_static_rule(rule, 'source') }}  {%     endfor %}  {% endif %} +    } +} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index c0780dad5..9d609f73f 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -175,7 +175,7 @@ table ip6 vyos_filter {  {%                 endif %}  {%             endfor %}  {%         endif %} -        {{ conf | nft_default_rule(name_text) }} +        {{ conf | nft_default_rule(name_text, ipv6=True) }}      }  {%     endfor %}  {%     for set_name in ns.sets %} diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 808e9dbe7..bf4be23ff 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -461,6 +461,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}  {% if parameters.bestpath.med is vyos_defined %}   bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is vyos_defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is vyos_defined }}  {% endif %} +{% if parameters.bestpath.peer_type is vyos_defined %} + bgp bestpath peer-type {{ 'multipath-relax' if parameters.bestpath.peer_type.multipath_relax is vyos_defined }} +{% endif %}  {% if parameters.cluster_id is vyos_defined %}   bgp cluster-id {{ parameters.cluster_id }}  {% endif %} diff --git a/data/templates/frr/isisd.frr.j2 b/data/templates/frr/isisd.frr.j2 index 709484c98..e0f3b393e 100644 --- a/data/templates/frr/isisd.frr.j2 +++ b/data/templates/frr/isisd.frr.j2 @@ -127,8 +127,7 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }}   segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }}  {%                     if prefix_config.absolute.explicit_null is vyos_defined %}   segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} explicit-null -{%                     endif %} -{%                     if prefix_config.absolute.no_php_flag is vyos_defined %} +{%                     elif prefix_config.absolute.no_php_flag is vyos_defined %}   segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} no-php-flag  {%                     endif %}  {%                 endif %} @@ -138,8 +137,7 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }}   segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }}  {%                     if prefix_config.index.explicit_null is vyos_defined %}   segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} explicit-null -{%                     endif %} -{%                     if prefix_config.index.no_php_flag is vyos_defined %} +{%                     elif prefix_config.index.no_php_flag is vyos_defined %}   segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} no-php-flag  {%                     endif %}  {%                 endif %} diff --git a/data/templates/ipsec/swanctl.conf.j2 b/data/templates/ipsec/swanctl.conf.j2 index bf6b8259c..38d7981c6 100644 --- a/data/templates/ipsec/swanctl.conf.j2 +++ b/data/templates/ipsec/swanctl.conf.j2 @@ -63,9 +63,11 @@ secrets {  {%             if peer_conf.local_address is vyos_defined %}          id-local = {{ peer_conf.local_address }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }}  {%             endif %} -        id-remote = {{ peer }} -{%             if peer_conf.authentication.id is vyos_defined %} -        id-localid = {{ peer_conf.authentication.id }} +{%             for address in peer_conf.remote_address %} +        id-remote_{{ address | dot_colon_to_dash }} = {{ address }} +{%             endfor %} +{%             if peer_conf.authentication.local_id is vyos_defined %} +        id-localid = {{ peer_conf.authentication.local_id }}  {%             endif %}  {%             if peer_conf.authentication.remote_id is vyos_defined %}          id-remoteid = {{ peer_conf.authentication.remote_id }} @@ -93,8 +95,8 @@ secrets {  {%     for ra, ra_conf in remote_access.connection.items() if ra_conf.disable is not vyos_defined %}  {%         if ra_conf.authentication.server_mode is vyos_defined('pre-shared-secret') %}      ike_{{ ra }} { -{%             if ra_conf.authentication.id is vyos_defined %} -        id = "{{ ra_conf.authentication.id }}" +{%             if ra_conf.authentication.local_id is vyos_defined %} +        id = "{{ ra_conf.authentication.local_id }}"  {%             elif ra_conf.local_address is vyos_defined %}          id = "{{ ra_conf.local_address }}"  {%             endif %} diff --git a/data/templates/ipsec/swanctl/peer.j2 b/data/templates/ipsec/swanctl/peer.j2 index 90d2c774f..d097a04fc 100644 --- a/data/templates/ipsec/swanctl/peer.j2 +++ b/data/templates/ipsec/swanctl/peer.j2 @@ -2,14 +2,14 @@  {% set name = peer.replace("@", "") | dot_colon_to_dash %}  {# peer needs to reference the global IKE configuration for certain values #}  {% set ike = ike_group[peer_conf.ike_group] %} -    peer_{{ name }} { +    {{ name }} {          proposals = {{ ike | get_esp_ike_cipher | join(',') }}          version = {{ ike.key_exchange[4:] if ike.key_exchange is vyos_defined else "0" }}  {% if peer_conf.virtual_address is vyos_defined %}          vips = {{ peer_conf.virtual_address | join(', ') }}  {% endif %} -        local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '0.0.0.0/0' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} -        remote_addrs = {{ peer if peer not in ['any', '0.0.0.0'] and peer[0:1] != '@' else '0.0.0.0/0' }} +        local_addrs = {{ peer_conf.local_address if peer_conf.local_address != 'any' else '%any' }} # dhcp:{{ peer_conf.dhcp_interface if 'dhcp_interface' in peer_conf else 'no' }} +        remote_addrs = {{ peer_conf.remote_address | join(",") if peer_conf.remote_address is vyos_defined and 'any' not in peer_conf.remote_address else '%any' }}  {% if peer_conf.authentication.mode is vyos_defined('x509') %}          send_cert = always  {% endif %} @@ -21,7 +21,7 @@          aggressive = yes  {% endif %}          rekey_time = {{ ike.lifetime }}s -        mobike = {{ "yes" if ike.mobike is not defined or ike.mobike == "enable" else "no" }} +        mobike = {{ "no" if ike.disable_mobike is defined else "yes" }}  {% if peer[0:1] == '@' %}          keyingtries = 0          reauth_time = 0 @@ -30,12 +30,12 @@  {% elif peer_conf.connection_type is vyos_defined('respond') %}          keyingtries = 1  {% endif %} -{% if peer_conf.force_encapsulation is vyos_defined('enable') %} +{% if peer_conf.force_udp_encapsulation is vyos_defined %}          encap = yes  {% endif %}          local { -{% if peer_conf.authentication.id is vyos_defined %} -            id = "{{ peer_conf.authentication.id }}" +{% if peer_conf.authentication.local_id is vyos_defined %} +            id = "{{ peer_conf.authentication.local_id }}"  {% endif %}              auth = {{ 'psk' if peer_conf.authentication.mode == 'pre-shared-secret' else 'pubkey' }}  {% if peer_conf.authentication.mode == 'x509' %} @@ -58,7 +58,7 @@          children {  {% if peer_conf.vti.bind is vyos_defined and peer_conf.tunnel is not vyos_defined %}  {%     set vti_esp = esp_group[ peer_conf.vti.esp_group ] if peer_conf.vti.esp_group is vyos_defined else esp_group[ peer_conf.default_esp_group ] %} -            peer_{{ name }}_vti { +            {{ name }}-vti {                  esp_proposals = {{ vti_esp | get_esp_ike_cipher(ike) | join(',') }}  {%     if vti_esp.life_bytes is vyos_defined %}                  life_bytes = {{ vti_esp.life_bytes }} @@ -75,7 +75,7 @@  {%     set if_id = peer_conf.vti.bind | replace('vti', '') | int + 1 %}                  if_id_in = {{ if_id }}                  if_id_out = {{ if_id }} -                ipcomp = {{ 'yes' if vti_esp.compression is vyos_defined('enable') else 'no' }} +                ipcomp = {{ 'yes' if vti_esp.compression is vyos_defined else 'no' }}                  mode = {{ vti_esp.mode }}  {%     if peer[0:1] == '@' %}                  start_action = none @@ -101,7 +101,7 @@  {%         set local_suffix = '[{0}/{1}]'.format(proto, local_port) if proto or local_port else '' %}  {%         set remote_port = tunnel_conf.remote.port if tunnel_conf.remote.port is vyos_defined else '' %}  {%         set remote_suffix = '[{0}/{1}]'.format(proto, remote_port) if proto or remote_port else '' %} -            peer_{{ name }}_tunnel_{{ tunnel_id }} { +            {{ name }}-tunnel-{{ tunnel_id }} {                  esp_proposals = {{ tunnel_esp | get_esp_ike_cipher(ike) | join(',') }}  {%         if tunnel_esp.life_bytes is vyos_defined %}                  life_bytes = {{ tunnel_esp.life_bytes }} @@ -126,7 +126,7 @@                  local_ts = {{ peer_conf.local_address }}{{ local_suffix }}                  remote_ts = {{ peer }}{{ remote_suffix }}  {%         endif %} -                ipcomp = {{ 'yes' if tunnel_esp.compression is vyos_defined('enable') else 'no' }} +                ipcomp = {{ 'yes' if tunnel_esp.compression is vyos_defined else 'no' }}                  mode = {{ tunnel_esp.mode }}  {%         if peer[0:1] == '@' %}                  start_action = none @@ -152,7 +152,7 @@  {%         endif %}              }  {%         if tunnel_conf.passthrough is vyos_defined %} -            peer_{{ name }}_tunnel_{{ tunnel_id }}_passthrough { +            {{ name }}-tunnel-{{ tunnel_id }}-passthrough {                  local_ts = {{ tunnel_conf.passthrough | join(",") }}                  remote_ts = {{ tunnel_conf.passthrough | join(",") }}                  start_action = trap diff --git a/data/templates/ipsec/swanctl/remote_access.j2 b/data/templates/ipsec/swanctl/remote_access.j2 index d2760ec1f..60d2d1807 100644 --- a/data/templates/ipsec/swanctl/remote_access.j2 +++ b/data/templates/ipsec/swanctl/remote_access.j2 @@ -17,9 +17,9 @@          pools = {{ rw_conf.pool | join(',') }}  {% endif %}          local { -{% if rw_conf.authentication.id is vyos_defined and rw_conf.authentication.use_x509_id is not vyos_defined %} +{% if rw_conf.authentication.local_id is vyos_defined and rw_conf.authentication.use_x509_id is not vyos_defined %}  {#          please use " quotes - else Apple iOS goes crazy #} -            id = "{{ rw_conf.authentication.id }}" +            id = "{{ rw_conf.authentication.local_id }}"  {% endif %}  {% if rw_conf.authentication.server_mode == 'x509' %}              auth = pubkey diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2 index e0cad5181..3194354e6 100644 --- a/data/templates/ocserv/ocserv_config.j2 +++ b/data/templates/ocserv/ocserv_config.j2 @@ -1,5 +1,9 @@  ### generated by vpn_openconnect.py ### +{% if listen_address is vyos_defined %} +listen-host = {{ listen_address }} +{% endif %} +  tcp-port = {{ listen_ports.tcp }}  udp-port = {{ listen_ports.udp }} @@ -7,7 +11,7 @@ run-as-user = nobody  run-as-group = daemon  {% if "radius" in authentication.mode %} -auth = "radius [config=/run/ocserv/radiusclient.conf]" +auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]"  {% elif "local" in authentication.mode %}  {%     if authentication.mode.local == "password-otp" %}  auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]" @@ -62,6 +66,13 @@ device = sslvpn  dns = {{ dns }}  {%     endfor %}  {% endif %} +{% if network_settings.tunnel_all_dns is vyos_defined %} +{%     if "yes" in network_settings.tunnel_all_dns %} +tunnel-all-dns = true +{%     else %} +tunnel-all-dns = false +{%     endif %} +{% endif %}  # IPv4 network pool  {% if network_settings.client_ip_settings.subnet is vyos_defined %} @@ -85,3 +96,10 @@ route = {{ route }}  split-dns = {{ tmp }}  {%     endfor %}  {% endif %} + +{% if authentication.group is vyos_defined %} +# Group settings +{%     for grp in authentication.group %} +select-group = {{ grp }} +{%     endfor %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 index 6b395692b..2d14230ae 100644 --- a/data/templates/telegraf/telegraf.j2 +++ b/data/templates/telegraf/telegraf.j2 @@ -45,7 +45,7 @@  ### Prometheus ###  [[outputs.prometheus_client]]    ## Address to listen on -  listen = "{{ prometheus_client.listen_address if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}" +  listen = "{{ prometheus_client.listen_address | bracketize_ipv6 if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}"    metric_version = {{ prometheus_client.metric_version }}  {%     if prometheus_client.authentication.username is vyos_defined and prometheus_client.authentication.password is vyos_defined  %}    ## Use HTTP Basic Authentication diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf index cd815148e..11a5bc7bf 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -1,61 +1,9 @@  #!/usr/sbin/nft -f -table ip vyos_static_nat { -    chain PREROUTING { -        type nat hook prerouting priority -100; policy accept; -        counter jump VYOS_PRE_DNAT_HOOK -    } - -    chain POSTROUTING { -        type nat hook postrouting priority 100; policy accept; -        counter jump VYOS_PRE_SNAT_HOOK -    } - -    chain VYOS_PRE_DNAT_HOOK { -        return -    } - -    chain VYOS_PRE_SNAT_HOOK { -        return -    } -} - +# Required by wanloadbalance  table ip nat { -    chain PREROUTING { -        type nat hook prerouting priority -100; policy accept; -        counter jump VYOS_PRE_DNAT_HOOK -    } - -    chain POSTROUTING { -        type nat hook postrouting priority 100; policy accept; -        counter jump VYOS_PRE_SNAT_HOOK -    } - -    chain VYOS_PRE_DNAT_HOOK { -        return -    } -      chain VYOS_PRE_SNAT_HOOK { -        return -    } -} - -table ip6 nat { -    chain PREROUTING { -        type nat hook prerouting priority -100; policy accept; -        counter jump VYOS_DNPT_HOOK -    } - -    chain POSTROUTING { -        type nat hook postrouting priority 100; policy accept; -        counter jump VYOS_SNPT_HOOK -    } - -    chain VYOS_DNPT_HOOK { -        return -    } - -    chain VYOS_SNPT_HOOK { +        type nat hook postrouting priority 99; policy accept;          return      }  } diff --git a/debian/control b/debian/control index 0db098be6..1f2151284 100644 --- a/debian/control +++ b/debian/control @@ -58,6 +58,7 @@ Depends:    frr-pythontools,    frr-rpki-rtrlib,    frr-snmp, +  libpam-google-authenticator,    grc,    hostapd,    hvinfo, diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index d39dddc77..773e86f00 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -316,7 +316,7 @@        </node>        <tagNode name="interface">          <properties> -          <help>Interface name</help> +          <help>Interface name to apply firewall configuration</help>            <completionHelp>              <script>${vyos_completion_dir}/list_interfaces.py</script>            </completionHelp> @@ -379,6 +379,14 @@            #include <include/firewall/default-action.xml.i>            #include <include/firewall/enable-default-log.xml.i>            #include <include/generic-description.xml.i> +          <leafNode name="default-jump-target"> +            <properties> +              <help>Set jump target. Action jump must be defined in default-action to use this setting</help> +              <completionHelp> +                <path>firewall ipv6-name</path> +              </completionHelp> +            </properties> +          </leafNode>            <tagNode name="rule">              <properties>                <help>Firewall rule number (IPv6)</help> @@ -452,6 +460,14 @@                    #include <include/firewall/icmpv6-type-name.xml.i>                  </children>                </node> +              <leafNode name="jump-target"> +                <properties> +                  <help>Set jump target. Action jump must be defined to use this setting</help> +                  <completionHelp> +                    <path>firewall ipv6-name</path> +                  </completionHelp> +                </properties> +              </leafNode>              </children>            </tagNode>          </children> @@ -527,6 +543,14 @@            #include <include/firewall/default-action.xml.i>            #include <include/firewall/enable-default-log.xml.i>            #include <include/generic-description.xml.i> +          <leafNode name="default-jump-target"> +            <properties> +              <help>Set jump target. Action jump must be defined in default-action to use this setting</help> +              <completionHelp> +                <path>firewall name</path> +              </completionHelp> +            </properties> +          </leafNode>            <tagNode name="rule">              <properties>                <help>Firewall rule number (IPv4)</help> @@ -599,6 +623,14 @@                    #include <include/firewall/icmp-type-name.xml.i>                  </children>                </node> +              <leafNode name="jump-target"> +                <properties> +                  <help>Set jump target. Action jump must be defined to use this setting</help> +                  <completionHelp> +                    <path>firewall name</path> +                  </completionHelp> +                </properties> +              </leafNode>                #include <include/firewall/ttl.xml.i>              </children>            </tagNode> diff --git a/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i index 01cf0e040..774741a5e 100644 --- a/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i +++ b/interface-definitions/include/accel-ppp/client-ipv6-pool.xml.i @@ -16,19 +16,19 @@          </constraint>        </properties>        <children> -          <leafNode name="mask"> -            <properties> -              <help>Prefix length used for individual client</help> -              <valueHelp> -                <format>u32:48-128</format> -                <description>Client prefix length</description> -              </valueHelp> -              <constraint> -                <validator name="numeric" argument="--range 48-128"/> -              </constraint> -            </properties> -            <defaultValue>64</defaultValue> -          </leafNode> +        <leafNode name="mask"> +          <properties> +            <help>Prefix length used for individual client</help> +            <valueHelp> +              <format>u32:48-128</format> +              <description>Client prefix length</description> +            </valueHelp> +            <constraint> +              <validator name="numeric" argument="--range 48-128"/> +            </constraint> +          </properties> +          <defaultValue>64</defaultValue> +        </leafNode>        </children>      </tagNode>      <tagNode name="delegate"> diff --git a/interface-definitions/include/accel-ppp/vlan.xml.i b/interface-definitions/include/accel-ppp/vlan.xml.i new file mode 100644 index 000000000..5ef4de633 --- /dev/null +++ b/interface-definitions/include/accel-ppp/vlan.xml.i @@ -0,0 +1,20 @@ +<!-- include start from accel-ppp/vlan.xml.i --> +<leafNode name="vlan"> +  <properties> +    <help>VLAN monitor for automatic creation of VLAN interfaces</help> +    <valueHelp> +      <format>u32:1-4094</format> +      <description>VLAN for automatic creation</description> +    </valueHelp> +    <valueHelp> +      <format>start-end</format> +      <description>VLAN range for automatic creation (e.g. 1-4094)</description> +    </valueHelp> +    <constraint> +      <validator name="numeric" argument="--allow-range --range 1-4094"/> +    </constraint> +    <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage> +    <multi/> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index d2bcea62a..70176144d 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1135,6 +1135,19 @@              </leafNode>            </children>          </node> +        <node name="peer-type"> +          <properties> +            <help>Peer type</help> +          </properties> +          <children> +            <leafNode name="multipath-relax"> +              <properties> +                <help>Allow load sharing across routes learned from different peer types</help> +                <valueless/> +              </properties> +            </leafNode> +          </children> +        </node>        </children>      </node>      <leafNode name="cluster-id"> diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i index 512cc23bd..468340cbb 100644 --- a/interface-definitions/include/firewall/action.xml.i +++ b/interface-definitions/include/firewall/action.xml.i @@ -3,22 +3,30 @@    <properties>      <help>Rule action</help>      <completionHelp> -      <list>accept reject drop</list> +      <list>accept jump reject return drop</list>      </completionHelp>      <valueHelp>        <format>accept</format>        <description>Accept matching entries</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>      <constraint> -      <regex>(accept|reject|drop)</regex> +      <regex>(accept|jump|reject|return|drop)</regex>      </constraint>    </properties>  </leafNode> diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i index 92a2fcaaf..80efaf335 100644 --- a/interface-definitions/include/firewall/default-action.xml.i +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -3,22 +3,30 @@    <properties>      <help>Default-action for rule-set</help>      <completionHelp> -      <list>drop reject accept</list> +      <list>drop jump reject return accept</list>      </completionHelp>      <valueHelp>        <format>drop</format>        <description>Drop if no prior rules are hit</description>      </valueHelp>      <valueHelp> +      <format>jump</format> +      <description>Jump to another chain if no prior rules are hit</description> +    </valueHelp> +    <valueHelp>        <format>reject</format>        <description>Drop and notify source if no prior rules are hit</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>accept</format>        <description>Accept if no prior rules are hit</description>      </valueHelp>      <constraint> -      <regex>(drop|reject|accept)</regex> +      <regex>(drop|jump|reject|return|accept)</regex>      </constraint>    </properties>    <defaultValue>drop</defaultValue> diff --git a/interface-definitions/include/firewall/dscp.xml.i b/interface-definitions/include/firewall/dscp.xml.i index 642212d7e..dd4da4894 100644 --- a/interface-definitions/include/firewall/dscp.xml.i +++ b/interface-definitions/include/firewall/dscp.xml.i @@ -11,8 +11,7 @@        <description>DSCP range to match</description>      </valueHelp>      <constraint> -      <validator name="numeric" argument="--range 0-63"/> -      <validator name="range" argument="--min=0 --max=63"/> +      <validator name="numeric" argument="--allow-range --range 0-63"/>      </constraint>      <multi/>    </properties> @@ -29,8 +28,7 @@        <description>DSCP range not to match</description>      </valueHelp>      <constraint> -      <validator name="numeric" argument="--range 0-63"/> -      <validator name="range" argument="--min=0 --max=63"/> +      <validator name="numeric" argument="--allow-range --range 0-63"/>      </constraint>      <multi/>    </properties> diff --git a/interface-definitions/include/firewall/packet-length.xml.i b/interface-definitions/include/firewall/packet-length.xml.i index 043f56d16..fd2eb67b0 100644 --- a/interface-definitions/include/firewall/packet-length.xml.i +++ b/interface-definitions/include/firewall/packet-length.xml.i @@ -11,8 +11,7 @@        <description>Packet length range to match</description>      </valueHelp>      <constraint> -      <validator name="numeric" argument="--range 1-65535"/> -      <validator name="range" argument="--min=1 --max=65535"/> +      <validator name="numeric" argument="--allow-range --range 1-65535"/>      </constraint>      <multi/>    </properties> @@ -29,8 +28,7 @@        <description>Packet length range not to match</description>      </valueHelp>      <constraint> -      <validator name="numeric" argument="--range 1-65535"/> -      <validator name="range" argument="--min=1 --max=65535"/> +      <validator name="numeric" argument="--allow-range --range 1-65535"/>      </constraint>      <multi/>    </properties> diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i index 5a7b5a8d3..e2ce7b9fd 100644 --- a/interface-definitions/include/firewall/tcp-flags.xml.i +++ b/interface-definitions/include/firewall/tcp-flags.xml.i @@ -126,8 +126,7 @@            <description>TCP MSS range (use '-' as delimiter)</description>          </valueHelp>          <constraint> -          <validator name="numeric" argument="--range 1-16384"/> -          <validator name="range" argument="--min=1 --max=16384"/> +          <validator name="numeric" argument="--allow-range --range 1-16384"/>          </constraint>        </properties>      </leafNode> diff --git a/interface-definitions/include/interface/adjust-mss.xml.i b/interface-definitions/include/interface/adjust-mss.xml.i index 41140ffe1..2b184a05e 100644 --- a/interface-definitions/include/interface/adjust-mss.xml.i +++ b/interface-definitions/include/interface/adjust-mss.xml.i @@ -11,11 +11,11 @@        <description>Automatically sets the MSS to the proper value</description>      </valueHelp>      <valueHelp> -      <format>u32:500-65535</format> +      <format>u32:536-65535</format>        <description>TCP Maximum segment size in bytes</description>      </valueHelp>      <constraint> -      <validator name="numeric" argument="--range 500-65535"/> +      <validator name="numeric" argument="--range 536-65535"/>        <regex>(clamp-mss-to-pmtu)</regex>      </constraint>    </properties> diff --git a/interface-definitions/include/ipsec/authentication-id.xml.i b/interface-definitions/include/ipsec/authentication-id.xml.i index 4967782ec..4e0b848c3 100644 --- a/interface-definitions/include/ipsec/authentication-id.xml.i +++ b/interface-definitions/include/ipsec/authentication-id.xml.i @@ -1,10 +1,10 @@  <!-- include start from ipsec/authentication-id.xml.i --> -<leafNode name="id"> +<leafNode name="local-id">    <properties> -    <help>ID for peer authentication</help> +    <help>Local ID for peer authentication</help>      <valueHelp>        <format>txt</format> -      <description>ID used for peer authentication</description> +      <description>Local ID used for peer authentication</description>      </valueHelp>    </properties>  </leafNode> diff --git a/interface-definitions/include/ipsec/remote-address.xml.i b/interface-definitions/include/ipsec/remote-address.xml.i new file mode 100644 index 000000000..ba96290d0 --- /dev/null +++ b/interface-definitions/include/ipsec/remote-address.xml.i @@ -0,0 +1,30 @@ +<!-- include start from ipsec/remote-address.xml.i --> +<leafNode name="remote-address"> +  <properties> +    <help>IPv4 or IPv6 address of the remote peer</help> +    <valueHelp> +      <format>ipv4</format> +      <description>IPv4 address of the remote peer</description> +    </valueHelp> +    <valueHelp> +      <format>ipv6</format> +      <description>IPv6 address of the remote peer</description> +    </valueHelp> +    <valueHelp> +      <format>hostname</format> +      <description>Fully qualified domain name of the remote peer</description> +    </valueHelp> +    <valueHelp> +      <format>any</format> +      <description>Allow any IP address of the remote peer</description> +    </valueHelp> +    <constraint> +      <validator name="ipv4-address"/> +      <validator name="ipv6-address"/> +      <validator name="fqdn"/> +      <regex>(any)</regex> +    </constraint> +    <multi/> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i new file mode 100644 index 000000000..b5841cabb --- /dev/null +++ b/interface-definitions/include/listen-address-single.xml.i @@ -0,0 +1,22 @@ +<leafNode name="listen-address"> +  <properties> +    <help>Local IP addresses to listen on</help> +    <completionHelp> +      <script>${vyos_completion_dir}/list_local_ips.sh --both</script> +    </completionHelp> +    <valueHelp> +      <format>ipv4</format> +      <description>IPv4 address to listen for incoming connections</description> +    </valueHelp> +    <valueHelp> +      <format>ipv6</format> +      <description>IPv6 address to listen for incoming connections</description> +    </valueHelp> +    <constraint> +      <validator name="ipv4-address"/> +      <validator name="ipv6-address"/> +      <validator name="ipv6-link-local"/> +    </constraint> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/url.xml.i b/interface-definitions/include/url.xml.i new file mode 100644 index 000000000..caa6f67bd --- /dev/null +++ b/interface-definitions/include/url.xml.i @@ -0,0 +1,15 @@ +<!-- include start from url.xml.i --> +<leafNode name="url"> +  <properties> +    <help>Remote URL</help> +    <valueHelp> +      <format>url</format> +      <description>Remote URL</description> +    </valueHelp> +    <constraint> +      <regex>^https?:\/\/?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\:[0-9]+)*(\/.*)?</regex> +    </constraint> +    <constraintErrorMessage>Incorrect URL format</constraintErrorMessage> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/version/ipsec-version.xml.i b/interface-definitions/include/version/ipsec-version.xml.i index 59295cc91..1c978e8e6 100644 --- a/interface-definitions/include/version/ipsec-version.xml.i +++ b/interface-definitions/include/version/ipsec-version.xml.i @@ -1,3 +1,3 @@  <!-- include start from include/version/ipsec-version.xml.i --> -<syntaxVersion component='ipsec' version='9'></syntaxVersion> +<syntaxVersion component='ipsec' version='10'></syntaxVersion>  <!-- include end --> diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i index ec81487f8..6bdd8d75c 100644 --- a/interface-definitions/include/version/pppoe-server-version.xml.i +++ b/interface-definitions/include/version/pppoe-server-version.xml.i @@ -1,3 +1,3 @@  <!-- include start from include/version/pppoe-server-version.xml.i --> -<syntaxVersion component='pppoe-server' version='5'></syntaxVersion> +<syntaxVersion component='pppoe-server' version='6'></syntaxVersion>  <!-- include end --> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index ab65a93f3..77f130e1c 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -93,6 +93,12 @@                    <valueless/>                  </properties>                </leafNode> +              <leafNode name="rfs"> +                <properties> +                  <help>Enable Receive Flow Steering</help> +                  <valueless/> +                </properties> +              </leafNode>                <leafNode name="sg">                  <properties>                    <help>Enable Scatter-Gather</help> diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in index cd3aa3638..ef8569437 100644 --- a/interface-definitions/service-ipoe-server.xml.in +++ b/interface-definitions/service-ipoe-server.xml.in @@ -10,30 +10,31 @@          <children>            <tagNode name="interface">              <properties> -              <help>Network interface to server IPoE</help> +              <help>Interface to listen dhcp or unclassified packets</help>                <completionHelp>                  <script>${vyos_completion_dir}/list_interfaces.py</script>                </completionHelp>              </properties>              <children> -              <leafNode name="network-mode"> +              <leafNode name="mode">                  <properties> -                  <help>Network Layer IPoE serves on</help> +                  <help>Client connectivity mode</help>                    <completionHelp> -                    <list>L2 L3</list> +                    <list>l2 l3</list>                    </completionHelp> -                  <constraint> -                    <regex>(L2|L3)</regex> -                  </constraint>                    <valueHelp> -                    <format>L2</format> -                    <description>client share the same subnet</description> +                    <format>l2</format> +                    <description>Client located on same interface as server</description>                    </valueHelp>                    <valueHelp> -                    <format>L3</format> -                    <description>clients are behind this router</description> +                    <format>l3</format> +                    <description>Client located behind a router</description>                    </valueHelp> +                  <constraint> +                    <regex>(l2|l3)</regex> +                  </constraint>                  </properties> +                <defaultValue>l2</defaultValue>                </leafNode>                <leafNode name="network">                  <properties> @@ -53,6 +54,7 @@                      <description>One VLAN per client</description>                    </valueHelp>                  </properties> +                <defaultValue>shared</defaultValue>                </leafNode>                <leafNode name="client-subnet">                  <properties> @@ -85,30 +87,19 @@                    </leafNode>                    <leafNode name="giaddr">                      <properties> -                      <help>address of the relay agent (Relay Agent IP Address)</help> +                      <help>Relay Agent IPv4 Address</help> +                      <valueHelp> +                        <format>ipv4</format> +                        <description>Gateway IP address</description> +                      </valueHelp> +                      <constraint> +                        <validator name="ipv4-address"/> +                      </constraint>                      </properties>                    </leafNode>                  </children>                </node> -              <leafNode name="vlan-id"> -                <properties> -                  <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> -                  <constraint> -                    <validator name="numeric" argument="--range 1-4096"/> -                  </constraint> -                  <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage> -                  <multi/> -                </properties> -              </leafNode> -              <leafNode name="vlan-range"> -                <properties> -                  <help>VLAN monitor for the automatic creation of vlans (user per vlan)</help> -                  <constraint> -                    <regex>(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})</regex> -                  </constraint> -                  <multi/> -                </properties> -              </leafNode> +              #include <include/accel-ppp/vlan.xml.i>              </children>            </tagNode>            #include <include/name-server-ipv4-ipv6.xml.i> @@ -120,6 +111,13 @@                <tagNode name="name">                  <properties>                    <help>Pool name</help> +                  <valueHelp> +                    <format>txt</format> +                    <description>Name of IP pool</description> +                  </valueHelp> +                  <constraint> +                    <regex>[-_a-zA-Z0-9.]+</regex> +                  </constraint>                  </properties>                  <children>                    #include <include/accel-ppp/gateway-address.xml.i> @@ -159,15 +157,15 @@                </leafNode>                <tagNode name="interface">                  <properties> -                  <help>Network interface the client mac will appear on</help> +                  <help>Network interface for client MAC addresses</help>                    <completionHelp>                      <script>${vyos_completion_dir}/list_interfaces.py</script>                    </completionHelp>                  </properties>                  <children> -                  <tagNode name="mac-address"> +                  <tagNode name="mac">                      <properties> -                      <help>Client mac address allowed to receive an IP address</help> +                      <help>Media Access Control (MAC) address</help>                        <valueHelp>                          <format>macaddr</format>                          <description>Hardware (MAC) address</description> @@ -200,13 +198,17 @@                            </leafNode>                          </children>                        </node> -                      <leafNode name="vlan-id"> +                      <leafNode name="vlan">                          <properties> -                          <help>VLAN-ID of the client network</help> +                          <help>VLAN monitor for automatic creation of VLAN interfaces</help> +                          <valueHelp> +                            <format>u32:1-4094</format> +                            <description>Client VLAN id</description> +                          </valueHelp>                            <constraint> -                            <validator name="numeric" argument="--range 1-4096"/> +                            <validator name="numeric" argument="--range 1-4094"/>                            </constraint> -                          <constraintErrorMessage>VLAN ID needs to be between 1 and 4096</constraintErrorMessage> +                          <constraintErrorMessage>VLAN IDs need to be in range 1-4094</constraintErrorMessage>                          </properties>                        </leafNode>                      </children> diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in index 68215dba4..47f943d83 100644 --- a/interface-definitions/service-monitoring-telegraf.xml.in +++ b/interface-definitions/service-monitoring-telegraf.xml.in @@ -228,7 +228,7 @@                        </constraint>                      </properties>                    </leafNode> -                  #include <include/listen-address.xml.i> +                  #include <include/listen-address-single.xml.i>                    <leafNode name="metric-version">                      <properties>                        <help>Metric version control mapping from Telegraf to Prometheus format</help> diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in index 50f42849b..b31109296 100644 --- a/interface-definitions/service-pppoe-server.xml.in +++ b/interface-definitions/service-pppoe-server.xml.in @@ -68,33 +68,7 @@                </completionHelp>              </properties>              <children> -              <leafNode name="vlan-id"> -                <properties> -                  <help>VLAN monitor for the automatic creation of single vlan</help> -                  <valueHelp> -                    <format>u32:1-4094</format> -                    <description>VLAN monitor for the automatic creation of single vlan</description> -                  </valueHelp> -                  <constraint> -                    <validator name="numeric" argument="--range 1-4094"/> -                  </constraint> -                  <constraintErrorMessage>VLAN ID needs to be between 1 and 4094</constraintErrorMessage> -                  <multi/> -                </properties> -              </leafNode> -              <leafNode name="vlan-range"> -                <properties> -                  <help>VLAN monitor for the automatic creation of vlans range</help> -                  <valueHelp> -                    <format>start-end</format> -                    <description>VLAN monitor range for the automatic creation of vlans (e.g. 1-4094)</description> -                  </valueHelp> -                  <constraint> -                    <validator name="range" argument="--min=1 --max=4094"/> -                  </constraint> -                  <multi/> -                </properties> -              </leafNode> +              #include <include/accel-ppp/vlan.xml.i>              </children>            </tagNode>            #include <include/accel-ppp/gateway-address.xml.i> diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in index 14f12b569..5810a97c6 100644 --- a/interface-definitions/system-conntrack.xml.in +++ b/interface-definitions/system-conntrack.xml.in @@ -259,13 +259,13 @@                </leafNode>                <leafNode name="max-retrans">                  <properties> -                  <help>TCP maximum retransmit attempts</help> +                  <help>Maximum number of packets that can be retransmitted without received an ACK</help>                    <valueHelp> -                    <format>u32:1-2147483647</format> -                    <description>Generic connection timeout in seconds</description> +                    <format>u32:1-255</format> +                    <description>Number of packets to be retransmitted</description>                    </valueHelp>                    <constraint> -                    <validator name="numeric" argument="--range 1-2147483647"/> +                    <validator name="numeric" argument="--range 1-255"/>                    </constraint>                  </properties>                  <defaultValue>3</defaultValue> diff --git a/interface-definitions/system-update-check.xml.in b/interface-definitions/system-update-check.xml.in new file mode 100644 index 000000000..e4d7041ec --- /dev/null +++ b/interface-definitions/system-update-check.xml.in @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interfaceDefinition> +  <node name="system"> +    <children> +      <node name="update-check" owner="${vyos_conf_scripts_dir}/system_update_check.py"> +        <properties> +          <help>Check available update images</help> +          <priority>9999</priority> +        </properties> +        <children> +          <leafNode name="auto-check"> +            <properties> +              <help>Enable auto check for new images</help> +              <valueless/> +            </properties> +          </leafNode> +          #include <include/url.xml.i> +        </children> +      </node> +    </children> +  </node> +</interfaceDefinition> diff --git a/interface-definitions/vpn-ipsec.xml.in b/interface-definitions/vpn-ipsec.xml.in index d36fbb024..4776c53dc 100644 --- a/interface-definitions/vpn-ipsec.xml.in +++ b/interface-definitions/vpn-ipsec.xml.in @@ -24,23 +24,9 @@              <children>                <leafNode name="compression">                  <properties> -                  <help>ESP compression</help> -                  <completionHelp> -                    <list>disable enable</list> -                  </completionHelp> -                  <valueHelp> -                    <format>disable</format> -                    <description>Disable ESP compression</description> -                  </valueHelp> -                  <valueHelp> -                    <format>enable</format> -                    <description>Enable ESP compression</description> -                  </valueHelp> -                  <constraint> -                    <regex>(disable|enable)</regex> -                  </constraint> +                  <help>Enable ESP compression</help> +                  <valueless/>                  </properties> -                <defaultValue>disable</defaultValue>                </leafNode>                <leafNode name="lifetime">                  <properties> @@ -309,20 +295,7 @@                <leafNode name="ikev2-reauth">                  <properties>                    <help>Re-authentication of the remote peer during an IKE re-key (IKEv2 only)</help> -                  <completionHelp> -                    <list>yes no</list> -                  </completionHelp> -                  <valueHelp> -                    <format>yes</format> -                    <description>Enable remote host re-authentication during an IKE rekey (currently broken due to a strongswan bug)</description> -                  </valueHelp> -                  <valueHelp> -                    <format>no</format> -                    <description>Disable remote host re-authenticaton during an IKE rekey</description> -                  </valueHelp> -                  <constraint> -                    <regex>(yes|no)</regex> -                  </constraint> +                  <valueless/>                  </properties>                </leafNode>                <leafNode name="key-exchange"> @@ -357,25 +330,11 @@                  </properties>                  <defaultValue>28800</defaultValue>                </leafNode> -              <leafNode name="mobike"> +              <leafNode name="disable-mobike">                  <properties> -                  <help>Enable MOBIKE Support (IKEv2 only)</help> -                  <completionHelp> -                    <list>enable disable</list> -                  </completionHelp> -                  <valueHelp> -                    <format>enable</format> -                    <description>Enable MOBIKE</description> -                  </valueHelp> -                  <valueHelp> -                    <format>disable</format> -                    <description>Disable MOBIKE</description> -                  </valueHelp> -                  <constraint> -                    <regex>(enable|disable)</regex> -                  </constraint> +                  <help>Disable MOBIKE Support (IKEv2 only)</help> +                  <valueless/>                  </properties> -                <defaultValue>enable</defaultValue>                </leafNode>                <leafNode name="mode">                  <properties> @@ -664,6 +623,14 @@            <tagNode name="profile">              <properties>                <help>VPN IPsec profile</help> +              <valueHelp> +                <format>txt</format> +                <description>Profile name</description> +              </valueHelp> +              <constraint> +                <regex>[a-zA-Z][0-9a-zA-Z_-]+</regex> +              </constraint> +              <constraintErrorMessage>Profile name must be alphanumeric and can contain hyphen(s) and underscore(s)</constraintErrorMessage>              </properties>              <children>                #include <include/generic-disable-node.xml.i> @@ -719,6 +686,14 @@                <tagNode name="connection">                  <properties>                    <help>IKEv2 VPN connection name</help> +                  <valueHelp> +                    <format>txt</format> +                    <description>Connection name</description> +                  </valueHelp> +                  <constraint> +                    <regex>[a-zA-Z][0-9a-zA-Z_-]+</regex> +                  </constraint> +                  <constraintErrorMessage>Profile name must be alphanumeric and can contain hyphen(s) and underscore(s)</constraintErrorMessage>                  </properties>                  <children>                    <node name="authentication"> @@ -929,23 +904,15 @@              <children>                <tagNode name="peer">                  <properties> -                  <help>VPN peer</help> -                  <valueHelp> -                    <format>ipv4</format> -                    <description>IPv4 address of the peer</description> -                  </valueHelp> -                  <valueHelp> -                    <format>ipv6</format> -                    <description>IPv6 address of the peer</description> -                  </valueHelp> +                  <help>Connection name of the peer</help>                    <valueHelp>                      <format>txt</format> -                    <description>Hostname of the peer</description> -                  </valueHelp> -                  <valueHelp> -                    <format><@text></format> -                    <description>ID of the peer</description> +                    <description>Connection name of the peer</description>                    </valueHelp> +                  <constraint> +                    <regex>[-_a-zA-Z0-9|@]+</regex> +                  </constraint> +                  <constraintErrorMessage>Peer connection name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>                  </properties>                  <children>                    #include <include/generic-disable-node.xml.i> @@ -1031,23 +998,10 @@                    </leafNode>                    #include <include/generic-description.xml.i>                    #include <include/dhcp-interface.xml.i> -                  <leafNode name="force-encapsulation"> +                  <leafNode name="force-udp-encapsulation">                      <properties> -                      <help>Force UDP Encapsulation for ESP payloads</help> -                      <completionHelp> -                        <list>enable disable</list> -                      </completionHelp> -                      <valueHelp> -                        <format>enable</format> -                        <description>Force UDP encapsulation</description> -                      </valueHelp> -                      <valueHelp> -                        <format>disable</format> -                        <description>Do not force UDP encapsulation</description> -                      </valueHelp> -                      <constraint> -                        <regex>(enable|disable)</regex> -                      </constraint> +                      <help>Force UDP encapsulation</help> +                      <valueless/>                      </properties>                    </leafNode>                    #include <include/ipsec/ike-group.xml.i> @@ -1075,6 +1029,7 @@                      </properties>                    </leafNode>                    #include <include/ipsec/local-address.xml.i> +                  #include <include/ipsec/remote-address.xml.i>                    <tagNode name="tunnel">                      <properties>                        <help>Peer tunnel</help> diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 6309863c5..bc7f78e79 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -50,6 +50,16 @@                    </leafNode>                  </children>                </node> +              <leafNode name="group"> +                <properties> +                  <help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help> +                  <valueHelp> +                    <format>txt</format> +                    <description>Group string. The group may be followed by a user-friendly name in brackets: group1[First Group]</description> +                  </valueHelp> +                  <multi/> +                </properties> +              </leafNode>                #include <include/auth-local-users.xml.i>                <node name="local-users">                  <children> @@ -144,10 +154,19 @@                      </properties>                      <defaultValue>2</defaultValue>                    </leafNode> +                  <leafNode name="groupconfig"> +                    <properties> +                      <help>If the groupconfig option is set, then config-per-user will be overriden, and all configuration will be read from radius.</help> +                    </properties> +                  </leafNode>                  </children>                </node>              </children>            </node> +          #include <include/listen-address-ipv4.xml.i> +          <leafNode name="listen-address"> +            <defaultValue>0.0.0.0</defaultValue> +          </leafNode>            <node name="listen-ports">              <properties>                <help>Specify custom ports to use for client connections</help> @@ -278,6 +297,26 @@                    <multi/>                  </properties>                </leafNode> +              <leafNode name="tunnel-all-dns"> +                <properties> +                  <help>If the tunnel-all-dns option is set to yes, tunnel all DNS queries via the VPN. This is the default when a default route is set.</help> +                  <completionHelp> +                    <list>yes no</list> +                  </completionHelp> +                  <valueHelp> +                    <format>yes</format> +                    <description>Enable tunneling of all DNS traffic</description> +                  </valueHelp> +                  <valueHelp> +                    <format>no</format> +                    <description>Disable tunneling of all DNS traffic</description> +                  </valueHelp> +                  <constraint> +                    <regex>(yes|no)</regex> +                  </constraint> +                </properties> +                <defaultValue>no</defaultValue> +              </leafNode>              </children>            </node>        </children> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index bd32992aa..4a0e6c3b2 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -164,6 +164,12 @@              </properties>              <command>${vyos_op_scripts_dir}/storage.py show</command>            </leafNode> +          <leafNode name="updates"> +            <properties> +              <help>Show system available updates</help> +            </properties> +            <command>${vyos_op_scripts_dir}/system.py show_update</command> +          </leafNode>            <leafNode name="uptime">              <properties>                <help>Show system uptime and load averages</help> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 912bc94f2..53decfbf5 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -643,7 +643,9 @@ def get_accel_dict(config, base, chap_secrets):      from vyos.util import get_half_cpus      from vyos.template import is_ipv4 -    dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) +    dict = config.get_config_dict(base, key_mangling=('-', '_'), +                                  get_first_key=True, +                                  no_tag_node_value_mangle=True)      # We have gathered the dict representation of the CLI, but there are default      # options which we need to update into the dictionary retrived. @@ -663,6 +665,18 @@ def get_accel_dict(config, base, chap_secrets):      # added to individual local users instead - so we can simply delete them      if dict_search('client_ipv6_pool.prefix.mask', default_values):          del default_values['client_ipv6_pool']['prefix']['mask'] +        # delete empty dicts +        if len (default_values['client_ipv6_pool']['prefix']) == 0: +            del default_values['client_ipv6_pool']['prefix'] +        if len (default_values['client_ipv6_pool']) == 0: +            del default_values['client_ipv6_pool'] + +    # T2665: IPoE only - it has an interface tag node +    # added to individual local users instead - so we can simply delete them +    if dict_search('authentication.interface', default_values): +        del default_values['authentication']['interface'] +    if dict_search('interface', default_values): +        del default_values['interface']      dict = dict_merge(default_values, dict) @@ -684,11 +698,9 @@ def get_accel_dict(config, base, chap_secrets):          dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6})          del dict['name_server'] -    # Add individual RADIUS server default values +    # T2665: Add individual RADIUS server default values      if dict_search('authentication.radius.server', dict): -        # T2665          default_values = defaults(base + ['authentication', 'radius', 'server']) -          for server in dict_search('authentication.radius.server', dict):              dict['authentication']['radius']['server'][server] = dict_merge(                  default_values, dict['authentication']['radius']['server'][server]) @@ -698,22 +710,31 @@ def get_accel_dict(config, base, chap_secrets):              if 'disable_accounting' in dict['authentication']['radius']['server'][server]:                  dict['authentication']['radius']['server'][server]['acct_port'] = '0' -    # Add individual local-user default values +    # T2665: Add individual local-user default values      if dict_search('authentication.local_users.username', dict): -        # T2665          default_values = defaults(base + ['authentication', 'local-users', 'username']) -          for username in dict_search('authentication.local_users.username', dict):              dict['authentication']['local_users']['username'][username] = dict_merge(                  default_values, dict['authentication']['local_users']['username'][username]) -    # Add individual IPv6 client-pool default mask if required +    # T2665: Add individual IPv6 client-pool default mask if required      if dict_search('client_ipv6_pool.prefix', dict): -        # T2665          default_values = defaults(base + ['client-ipv6-pool', 'prefix']) -          for prefix in dict_search('client_ipv6_pool.prefix', dict):              dict['client_ipv6_pool']['prefix'][prefix] = dict_merge(                  default_values, dict['client_ipv6_pool']['prefix'][prefix]) +    # T2665: IPoE only - add individual local-user default values +    if dict_search('authentication.interface', dict): +        default_values = defaults(base + ['authentication', 'interface']) +        for interface in dict_search('authentication.interface', dict): +            dict['authentication']['interface'][interface] = dict_merge( +                default_values, dict['authentication']['interface'][interface]) + +    if dict_search('interface', dict): +        default_values = defaults(base + ['interface']) +        for interface in dict_search('interface', dict): +            dict['interface'][interface] = dict_merge(default_values, +                                                      dict['interface'][interface]) +      return dict diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 447ec795c..afa0c5b33 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -381,14 +381,14 @@ def verify_vlan_config(config):              verify_mtu_parent(c_vlan, config)              verify_mtu_parent(c_vlan, s_vlan) -def verify_accel_ppp_base_service(config): +def verify_accel_ppp_base_service(config, local_users=True):      """      Common helper function which must be used by all Accel-PPP services based      on get_config_dict()      """      # vertify auth settings -    if dict_search('authentication.mode', config) == 'local': -        if not dict_search('authentication.local_users', config): +    if local_users and dict_search('authentication.mode', config) == 'local': +        if dict_search(f'authentication.local_users', config) == None:              raise ConfigError('Authentication mode local requires local users to be configured!')          for user in dict_search('authentication.local_users.username', config): diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index b56caef71..f9b7222fd 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -326,6 +326,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):      if 'action' in rule_conf:          output.append(nft_action(rule_conf['action'])) +        if 'jump' in rule_conf['action']: +            target = rule_conf['jump_target'] +            output.append(f'NAME{def_suffix}_{target}') +      else:          output.append('return') diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index b8deb3311..b260a00ef 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -16,6 +16,7 @@  import os  import re +from glob import glob  from vyos.ethtool import Ethtool  from vyos.ifconfig.interface import Interface  from vyos.util import run @@ -70,9 +71,9 @@ class EthernetIf(Interface):      }}      _sysfs_set = {**Interface._sysfs_set, **{ -        'rps': { -            'convert': lambda cpus: cpus if cpus else '0', -            'location': '/sys/class/net/{ifname}/queues/rx-0/rps_cpus', +        'rfs': { +            'convert': lambda num: num if num else '0', +            'location': '/proc/sys/net/core/rps_sock_flow_entries',          },      }} @@ -246,6 +247,7 @@ class EthernetIf(Interface):              raise ValueError('Value out of range')          rps_cpus = '0' +        queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))          if state:              # Enable RPS on all available CPUs except CPU0 which we will not              # utilize so the system has one spare core when it's under high @@ -255,8 +257,25 @@ class EthernetIf(Interface):              # Linux will clip that internally!              rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe' +        for i in range(0, queues): +            self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) +          # send bitmask representation as hex string without leading '0x' -        return self.set_interface('rps', rps_cpus) +        return True + +    def set_rfs(self, state): +        rfs_flow = 0 +        global_rfs_flow = 0 +        queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) +        if state: +            global_rfs_flow = 32768 +            rfs_flow = int(global_rfs_flow/queues) + +        self.set_interface('rfs', global_rfs_flow) +        for i in range(0, queues): +            self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow) + +        return True      def set_sg(self, state):          """ @@ -342,6 +361,9 @@ class EthernetIf(Interface):          # RPS - Receive Packet Steering          self.set_rps(dict_search('offload.rps', config) != None) +        # RFS - Receive Flow Steering +        self.set_rfs(dict_search('offload.rfs', config) != None) +          # scatter-gather option          self.set_sg(dict_search('offload.sg', config) != None) diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 28b5e2991..fe5e9c519 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>  #  # This library is free software; you can redistribute it and/or  # modify it under the terms of the GNU Lesser General Public @@ -17,11 +17,11 @@ import os  import time  from datetime import timedelta +from tempfile import NamedTemporaryFile  from hurry.filesize import size  from hurry.filesize import alternative -from vyos.config import Config  from vyos.ifconfig import Interface  from vyos.ifconfig import Operational  from vyos.template import is_ipv6 @@ -71,10 +71,11 @@ class WireGuardOperational(Operational):          return output      def show_interface(self): -        wgdump = self._dump().get(self.config['ifname'], None) - +        from vyos.config import Config          c = Config() +        wgdump = self._dump().get(self.config['ifname'], None) +          c.set_level(["interfaces", "wireguard", self.config['ifname']])          description = c.return_effective_value(["description"])          ips = c.return_effective_values(["address"]) @@ -167,64 +168,66 @@ class WireGuardIf(Interface):          # remove no longer associated peers first          if 'peer_remove' in config: -            for tmp in config['peer_remove']: -                peer = config['peer_remove'][tmp] -                peer['ifname'] = config['ifname'] +            for peer, public_key in config['peer_remove'].items(): +                self._cmd(f'wg set {self.ifname} peer {public_key} remove') -                cmd = 'wg set {ifname} peer {public_key} remove' -                self._cmd(cmd.format(**peer)) - -        config['private_key_file'] = '/tmp/tmp.wireguard.key' -        with open(config['private_key_file'], 'w') as f: -            f.write(config['private_key']) +        tmp_file = NamedTemporaryFile('w') +        tmp_file.write(config['private_key']) +        tmp_file.flush()          # Wireguard base command is identical for every peer -        base_cmd  = 'wg set {ifname} private-key {private_key_file}' +        base_cmd  = 'wg set {ifname}'          if 'port' in config:              base_cmd += ' listen-port {port}'          if 'fwmark' in config:              base_cmd += ' fwmark {fwmark}' +        base_cmd += f' private-key {tmp_file.name}'          base_cmd = base_cmd.format(**config) -        for tmp in config['peer']: -            peer = config['peer'][tmp] - -            # start of with a fresh 'wg' command -            cmd = base_cmd + ' peer {public_key}' - -            # If no PSK is given remove it by using /dev/null - passing keys via -            # the shell (usually bash) is considered insecure, thus we use a file -            no_psk_file = '/dev/null' -            psk_file = no_psk_file -            if 'preshared_key' in peer: -                psk_file = '/tmp/tmp.wireguard.psk' -                with open(psk_file, 'w') as f: -                    f.write(peer['preshared_key']) -            cmd += f' preshared-key {psk_file}' - -            # Persistent keepalive is optional -            if 'persistent_keepalive'in peer: -                cmd += ' persistent-keepalive {persistent_keepalive}' - -            # Multiple allowed-ip ranges can be defined - ensure we are always -            # dealing with a list -            if isinstance(peer['allowed_ips'], str): -                peer['allowed_ips'] = [peer['allowed_ips']] -            cmd += ' allowed-ips ' + ','.join(peer['allowed_ips']) - -            # Endpoint configuration is optional -            if {'address', 'port'} <= set(peer): -                if is_ipv6(peer['address']): -                    cmd += ' endpoint [{address}]:{port}' -                else: -                    cmd += ' endpoint {address}:{port}' +        if 'peer' in config: +            for peer, peer_config in config['peer'].items(): +                # T4702: No need to configure this peer when it was explicitly +                # marked as disabled - also active sessions are terminated as +                # the public key was already removed when entering this method! +                if 'disable' in peer_config: +                    continue + +                # start of with a fresh 'wg' command +                cmd = base_cmd + ' peer {public_key}' + +                # If no PSK is given remove it by using /dev/null - passing keys via +                # the shell (usually bash) is considered insecure, thus we use a file +                no_psk_file = '/dev/null' +                psk_file = no_psk_file +                if 'preshared_key' in peer_config: +                    psk_file = '/tmp/tmp.wireguard.psk' +                    with open(psk_file, 'w') as f: +                        f.write(peer_config['preshared_key']) +                cmd += f' preshared-key {psk_file}' + +                # Persistent keepalive is optional +                if 'persistent_keepalive'in peer_config: +                    cmd += ' persistent-keepalive {persistent_keepalive}' + +                # Multiple allowed-ip ranges can be defined - ensure we are always +                # dealing with a list +                if isinstance(peer_config['allowed_ips'], str): +                    peer_config['allowed_ips'] = [peer_config['allowed_ips']] +                cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips']) + +                # Endpoint configuration is optional +                if {'address', 'port'} <= set(peer_config): +                    if is_ipv6(peer_config['address']): +                        cmd += ' endpoint [{address}]:{port}' +                    else: +                        cmd += ' endpoint {address}:{port}' -            self._cmd(cmd.format(**peer)) +                self._cmd(cmd.format(**peer_config)) -            # PSK key file is not required to be stored persistently as its backed by CLI -            if psk_file != no_psk_file and os.path.exists(psk_file): -                os.remove(psk_file) +                # PSK key file is not required to be stored persistently as its backed by CLI +                if psk_file != no_psk_file and os.path.exists(psk_file): +                    os.remove(psk_file)          # call base class          super().update(config) diff --git a/python/vyos/nat.py b/python/vyos/nat.py new file mode 100644 index 000000000..31bbdc386 --- /dev/null +++ b/python/vyos/nat.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +from vyos.template import is_ip_network +from vyos.util import dict_search_args + +def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): +    output = [] +    ip_prefix = 'ip6' if ipv6 else 'ip' +    log_prefix = ('DST' if nat_type == 'destination' else 'SRC') + f'-NAT-{rule_id}' +    log_suffix = '' + +    if ipv6: +        log_prefix = log_prefix.replace("NAT-", "NAT66-") + +    ignore_type_addr = False +    translation_str = '' + +    if 'inbound_interface' in rule_conf: +        ifname = rule_conf['inbound_interface'] +        if ifname != 'any': +            output.append(f'iifname "{ifname}"') + +    if 'outbound_interface' in rule_conf: +        ifname = rule_conf['outbound_interface'] +        if ifname != 'any': +            output.append(f'oifname "{ifname}"') + +    if 'protocol' in rule_conf and rule_conf['protocol'] != 'all': +        protocol = rule_conf['protocol'] +        if protocol == 'tcp_udp': +            protocol = '{ tcp, udp }' +        output.append(f'meta l4proto {protocol}') + +    if 'exclude' in rule_conf: +        translation_str = 'return' +        log_suffix = '-EXCL' +    elif 'translation' in rule_conf: +        translation_prefix = nat_type[:1] +        translation_output = [f'{translation_prefix}nat'] +        addr = dict_search_args(rule_conf, 'translation', 'address') +        port = dict_search_args(rule_conf, 'translation', 'port') + +        if addr and is_ip_network(addr): +            if not ipv6: +                map_addr =  dict_search_args(rule_conf, nat_type, 'address') +                translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}') +                ignore_type_addr = True +            else: +                translation_output.append(f'prefix to {addr}') +        elif addr == 'masquerade': +            if port: +                addr = f'{addr} to ' +            translation_output = [addr] +            log_suffix = '-MASQ' +        else: +            translation_output.append('to') +            if addr: +                translation_output.append(addr) + +        options = [] +        addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') +        port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') +        if addr_mapping == 'persistent': +            options.append('persistent') +        if port_mapping and port_mapping != 'none': +            options.append(port_mapping) + +        translation_str = " ".join(translation_output) + (f':{port}' if port else '') + +        if options: +            translation_str += f' {",".join(options)}' + +    for target in ['source', 'destination']: +        prefix = target[:1] +        addr = dict_search_args(rule_conf, target, 'address') +        if addr and not (ignore_type_addr and target == nat_type): +            operator = '' +            if addr[:1] == '!': +                operator = '!=' +                addr = addr[1:] +            output.append(f'{ip_prefix} {prefix}addr {operator} {addr}') + +        addr_prefix = dict_search_args(rule_conf, target, 'prefix') +        if addr_prefix and ipv6: +            operator = '' +            if addr_prefix[:1] == '!': +                operator = '!=' +                addr_prefix = addr[1:] +            output.append(f'ip6 {prefix}addr {operator} {addr_prefix}') + +        port = dict_search_args(rule_conf, target, 'port') +        if port: +            protocol = rule_conf['protocol'] +            if protocol == 'tcp_udp': +                protocol = 'th' +            operator = '' +            if port[:1] == '!': +                operator = '!=' +                port = port[1:] +            output.append(f'{protocol} {prefix}port {operator} {{ {port} }}') + +    output.append('counter') + +    if 'log' in rule_conf: +        output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + +    if translation_str: +        output.append(translation_str) + +    output.append(f'comment "{log_prefix}"') + +    return " ".join(output) + +def parse_nat_static_rule(rule_conf, rule_id, nat_type): +    output = [] +    log_prefix = ('STATIC-DST' if nat_type == 'destination' else 'STATIC-SRC') + f'-NAT-{rule_id}' +    log_suffix = '' + +    ignore_type_addr = False +    translation_str = '' + +    if 'inbound_interface' in rule_conf: +        ifname = rule_conf['inbound_interface'] +        ifprefix = 'i' if nat_type == 'destination' else 'o' +        if ifname != 'any': +            output.append(f'{ifprefix}ifname "{ifname}"') + +    if 'exclude' in rule_conf: +        translation_str = 'return' +        log_suffix = '-EXCL' +    elif 'translation' in rule_conf: +        translation_prefix = nat_type[:1] +        translation_output = [f'{translation_prefix}nat'] +        addr = dict_search_args(rule_conf, 'translation', 'address') +        map_addr =  dict_search_args(rule_conf, 'destination', 'address') + +        if nat_type == 'source': +            addr, map_addr = map_addr, addr # Swap + +        if addr and is_ip_network(addr): +            translation_output.append(f'ip prefix to ip {translation_prefix}addr map {{ {map_addr} : {addr} }}') +            ignore_type_addr = True +        elif addr: +            translation_output.append(f'to {addr}') + +        options = [] +        addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') +        port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping') +        if addr_mapping == 'persistent': +            options.append('persistent') +        if port_mapping and port_mapping != 'none': +            options.append(port_mapping) + +        if options: +            translation_output.append(",".join(options)) + +        translation_str = " ".join(translation_output) + +    prefix = nat_type[:1] +    addr = dict_search_args(rule_conf, 'translation' if nat_type == 'source' else nat_type, 'address') +    if addr and not ignore_type_addr: +        output.append(f'ip {prefix}addr {addr}') + +    output.append('counter') + +    if translation_str: +        output.append(translation_str) + +    if 'log' in rule_conf: +        output.append(f'log prefix "[{log_prefix}{log_suffix}]"') + +    output.append(f'comment "{log_prefix}"') + +    return " ".join(output) diff --git a/python/vyos/template.py b/python/vyos/template.py index 9804308c1..0870a0523 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -548,7 +548,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):      return parse_rule(rule_conf, fw_name, rule_id, ip_name)  @register_filter('nft_default_rule') -def nft_default_rule(fw_conf, fw_name): +def nft_default_rule(fw_conf, fw_name, ipv6=False):      output = ['counter']      default_action = fw_conf['default_action'] @@ -557,6 +557,11 @@ def nft_default_rule(fw_conf, fw_name):          output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')      output.append(nft_action(default_action)) +    if 'default_jump_target' in fw_conf: +        target = fw_conf['default_jump_target'] +        def_suffix = '6' if ipv6 else '' +        output.append(f'NAME{def_suffix}_{target}') +      output.append(f'comment "{fw_name} default-action {default_action}"')      return " ".join(output) @@ -611,6 +616,25 @@ def nft_nested_group(out_list, includes, groups, key):          add_includes(name)      return out_list +@register_filter('nat_rule') +def nat_rule(rule_conf, rule_id, nat_type, ipv6=False): +    from vyos.nat import parse_nat_rule +    return parse_nat_rule(rule_conf, rule_id, nat_type, ipv6) + +@register_filter('nat_static_rule') +def nat_static_rule(rule_conf, rule_id, nat_type): +    from vyos.nat import parse_nat_static_rule +    return parse_nat_static_rule(rule_conf, rule_id, nat_type) + +@register_filter('range_to_regex') +def range_to_regex(num_range): +    from vyos.range_regex import range_to_regex +    if '-' not in num_range: +        return num_range + +    regex = range_to_regex(num_range) +    return f'({regex})' +  @register_test('vyos_defined')  def vyos_defined(value, test_value=None, var_type=None):      """ diff --git a/python/vyos/version.py b/python/vyos/version.py index 871bb0f1b..fb706ad44 100644 --- a/python/vyos/version.py +++ b/python/vyos/version.py @@ -31,6 +31,7 @@ Example of the version data dict::  import os  import json +import requests  import vyos.defaults  from vyos.util import read_file @@ -105,3 +106,41 @@ def get_full_version_data(fname=version_file):      version_data['hardware_uuid'] = read_file(subsystem + '/product_uuid', 'Unknown')      return version_data + +def get_remote_version(url): +    """ +    Get remote available JSON file from remote URL +    An example of the image-version.json + +    [ +       { +          "arch":"amd64", +          "flavors":[ +           "generic" +        ], +        "image":"vyos-rolling-latest.iso", +        "latest":true, +        "lts":false, +        "release_date":"2022-09-06", +        "release_train":"sagitta", +        "url":"http://xxx/rolling/current/vyos-rolling-latest.iso", +        "version":"vyos-1.4-rolling-202209060217" +      } +    ] +    """ +    headers = {} +    try: +        remote_data = requests.get(url=url, headers=headers) +        remote_data.raise_for_status() +        if remote_data.status_code != 200: +            return False +        return remote_data.json() +    except requests.exceptions.HTTPError as errh: +        print ("HTTP Error:", errh) +    except requests.exceptions.ConnectionError as errc: +        print ("Connecting error:", errc) +    except requests.exceptions.Timeout as errt: +        print ("Timeout error:", errt) +    except requests.exceptions.RequestException as err: +        print ("Unable to get remote data", err) +    return False diff --git a/smoketest/configs/ipoe-server b/smoketest/configs/ipoe-server new file mode 100644 index 000000000..a375e91de --- /dev/null +++ b/smoketest/configs/ipoe-server @@ -0,0 +1,119 @@ +interfaces { +    ethernet eth0 { +        address dhcp +    } +    ethernet eth1 { +        address 192.168.0.1/24 +    } +    ethernet eth2 { +    } +    loopback lo { +    } +} +nat { +    source { +        rule 100 { +            outbound-interface eth0 +            source { +                address 192.168.0.0/24 +            } +            translation { +                address masquerade +            } +        } +    } +} +service { +    ipoe-server { +        authentication { +            interface eth1 { +                mac-address 08:00:27:2f:d8:06 { +                    rate-limit { +                        download 1000 +                        upload 500 +                    } +                    vlan-id 100 +                } +            } +            interface eth2 { +                mac-address 08:00:27:2f:d8:06 { +                } +            } +            mode local +        } +        client-ip-pool { +            name POOL1 { +                gateway-address 192.0.2.1 +                subnet 192.0.2.0/24 +            } +        } +        client-ipv6-pool { +            delegate 2001:db8:1::/48 { +                delegation-prefix 56 +            } +            prefix 2001:db8::/48 { +                mask 64 +            } +        } +        interface eth1 { +            client-subnet 192.168.0.0/24 +            network vlan +            network-mode L3 +            vlan-id 100 +            vlan-id 200 +            vlan-range 1000-2000 +            vlan-range 2500-2700 +        } +        interface eth2 { +            client-subnet 192.168.1.0/24 +        } +        name-server 10.10.1.1 +        name-server 10.10.1.2 +        name-server 2001:db8:aaa:: +        name-server 2001:db8:bbb:: +    } +    ssh { +    } +} +system { +    config-management { +        commit-revisions 100 +    } +    console { +        device ttyS0 { +            speed 115200 +        } +    } +    host-name vyos +    login { +        user vyos { +            authentication { +                encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 +                plaintext-password "" +            } +        } +    } +    ntp { +        server 0.pool.ntp.org { +        } +        server 1.pool.ntp.org { +        } +        server 2.pool.ntp.org { +        } +    } +    syslog { +        global { +            facility all { +                level info +            } +            facility protocols { +                level debug +            } +        } +    } +} + + +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@1:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@13:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@6:salt@1:snmp@2:ssh@2:sstp@3:system@19:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webgui@1:webproxy@2:zone-policy@1" +// Release version: 1.3.1 diff --git a/smoketest/configs/pppoe-server b/smoketest/configs/pppoe-server index 7e4ccc80e..bfbef4a34 100644 --- a/smoketest/configs/pppoe-server +++ b/smoketest/configs/pppoe-server @@ -43,7 +43,13 @@ service {              stop 192.168.0.200          }          gateway-address 192.168.0.2 +        interface eth1 { +        }          interface eth2 { +            vlan-id 10 +            vlan-id 20 +            vlan-range 30-40 +            vlan-range 50-60          }          name-server 192.168.0.1      } diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index b2acb03cc..471bdaffb 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -27,6 +27,17 @@ from vyos.util import process_named_running  class BasicAccelPPPTest:      class TestCase(VyOSUnitTestSHIM.TestCase): + +        @classmethod +        def setUpClass(cls): +            cls._process_name = 'accel-pppd' + +            super(BasicAccelPPPTest.TestCase, cls).setUpClass() + +            # ensure we can also run this test on a live system - so lets clean +            # out the current configuration :) +            cls.cli_delete(cls, cls._base_path) +          def setUp(self):              self._gateway = '192.0.2.1'              # ensure we can also run this test on a live system - so lets clean @@ -34,9 +45,15 @@ class BasicAccelPPPTest:              self.cli_delete(self._base_path)          def tearDown(self): +            # Check for running process +            self.assertTrue(process_named_running(self._process_name)) +              self.cli_delete(self._base_path)              self.cli_commit() +            # Check for running process +            self.assertFalse(process_named_running(self._process_name)) +          def set(self, path):              self.cli_set(self._base_path + path) @@ -113,9 +130,6 @@ class BasicAccelPPPTest:              tmp = re.findall(regex, tmp)              self.assertTrue(tmp) -            # Check for running process -            self.assertTrue(process_named_running(self._process_name)) -              # Check local-users default value(s)              self.delete(['authentication', 'local-users', 'username', user, 'static-ip'])              # commit changes @@ -127,9 +141,6 @@ class BasicAccelPPPTest:              tmp = re.findall(regex, tmp)              self.assertTrue(tmp) -            # Check for running process -            self.assertTrue(process_named_running(self._process_name)) -          def test_accel_radius_authentication(self):              # Test configuration of RADIUS authentication for PPPoE server              self.basic_config() @@ -186,9 +197,6 @@ class BasicAccelPPPTest:              self.assertEqual(f'req-limit=0', server[4])              self.assertEqual(f'fail-time=0', server[5]) -            # Check for running process -            self.assertTrue(process_named_running(self._process_name)) -              #              # Disable Radius Accounting              # diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 8e4aac788..4b2cf9864 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -209,6 +209,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp'])          self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn'])          self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range]) +        self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return']) +        self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre'])          self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) @@ -222,12 +224,14 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):              ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'],              ['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'],              ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}'], +            ['meta l4proto gre', 'return']          ]          self.verify_nftables(nftables_search, 'ip vyos_filter')      def test_ipv4_advanced(self):          name = 'smoketest-adv' +        name2 = 'smoketest-adv2'          interface = 'eth0'          self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) @@ -246,6 +250,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp', '3-11'])          self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) +        self.cli_set(['firewall', 'name', name2, 'default-action', 'jump']) +        self.cli_set(['firewall', 'name', name2, 'default-jump-target', name]) +        self.cli_set(['firewall', 'name', name2, 'enable-default-log']) +        self.cli_set(['firewall', 'name', name2, 'rule', '1', 'source', 'address', '198.51.100.1']) +        self.cli_set(['firewall', 'name', name2, 'rule', '1', 'action', 'jump']) +        self.cli_set(['firewall', 'name', name2, 'rule', '1', 'jump-target', name]) +          self.cli_set(['firewall', 'interface', interface, 'in', 'name', name])          self.cli_commit() @@ -254,7 +265,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):              [f'iifname "{interface}"', f'jump NAME_{name}'],              ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'],              ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'], -            [f'log prefix "[{name}-default-D]"', 'drop'] +            [f'log prefix "[{name}-default-D]"', 'drop'], +            ['ip saddr 198.51.100.1', f'jump NAME_{name}'], +            [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}']          ]          self.verify_nftables(nftables_search, 'ip vyos_filter') @@ -276,6 +289,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888']) +        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'return']) +        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'protocol', 'gre']) +          self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])          self.cli_commit() @@ -284,13 +300,15 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):              [f'iifname "{interface}"', f'jump NAME6_{name}'],              ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'],              ['meta l4proto { tcp, udp }', 'th dport 8888', 'reject'], -            ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop'] +            ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop'], +            ['meta l4proto gre', 'return']          ]          self.verify_nftables(nftables_search, 'ip6 vyos_filter')      def test_ipv6_advanced(self):          name = 'v6-smoketest-adv' +        name2 = 'v6-smoketest-adv2'          interface = 'eth0'          self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop']) @@ -309,6 +327,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp', '4-14'])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp-exclude', '31-35']) +        self.cli_set(['firewall', 'ipv6-name', name2, 'default-action', 'jump']) +        self.cli_set(['firewall', 'ipv6-name', name2, 'default-jump-target', name]) +        self.cli_set(['firewall', 'ipv6-name', name2, 'enable-default-log']) +        self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'source', 'address', '2001:db8::/64']) +        self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'action', 'jump']) +        self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'jump-target', name]) +          self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name])          self.cli_commit() @@ -317,7 +342,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):              [f'iifname "{interface}"', f'jump NAME6_{name}'],              ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'return'],              ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'return'], -            [f'log prefix "[{name}-default-D]"', 'drop'] +            [f'log prefix "[{name}-default-D]"', 'drop'], +            ['ip6 saddr 2001:db8::/64', f'jump NAME6_{name}'], +            [f'log prefix "[{name2}-default-J]"', f'jump NAME6_{name}']          ]          self.verify_nftables(nftables_search, 'ip6 vyos_filter') diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 05d2ae5f5..5049bd5b0 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -17,6 +17,7 @@  import os  import re  import unittest +from glob import glob  from netifaces import AF_INET  from netifaces import AF_INET6 @@ -185,6 +186,43 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):              self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') +    def test_offloading_rfs(self): +        global_rfs_flow = 32768 +        rfs_flow = global_rfs_flow + +        for interface in self._interfaces: +            self.cli_set(self._base_path + [interface, 'offload', 'rfs']) + +        self.cli_commit() + +        for interface in self._interfaces: +            queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) +            rfs_flow = int(global_rfs_flow/queues) +            for i in range(0, queues): +                tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') +                self.assertEqual(int(tmp), rfs_flow) + +        tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries') +        self.assertEqual(int(tmp), global_rfs_flow) + + +        # delete configuration of RFS and check all values returned to default "0" +        for interface in self._interfaces: +            self.cli_delete(self._base_path + [interface, 'offload', 'rfs']) + +        self.cli_commit() + +        for interface in self._interfaces: +            queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) +            rfs_flow = int(global_rfs_flow/queues) +            for i in range(0, queues): +                tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') +                self.assertEqual(int(tmp), 0) + +        tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries') +        self.assertEqual(int(tmp), 0) + +      def test_non_existing_interface(self):          unknonw_interface = self._base_path + ['eth667']          self.cli_set(unknonw_interface) diff --git a/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancning_wan.py index 303dece86..23020b9b1 100755 --- a/smoketest/scripts/cli/test_load_balancning_wan.py +++ b/smoketest/scripts/cli/test_load_balancning_wan.py @@ -177,6 +177,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase):  }"""          nat_vyos_pre_snat_hook = """table ip nat {  	chain VYOS_PRE_SNAT_HOOK { +		type nat hook postrouting priority srcnat - 1; policy accept;  		counter jump WANLOADBALANCE  		return  	} diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 408facfb3..f824838c0 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -26,6 +26,7 @@ from vyos.util import dict_search  base_path = ['nat']  src_path = base_path + ['source']  dst_path = base_path + ['destination'] +static_path = base_path + ['static']  class TestNAT(VyOSUnitTestSHIM.TestCase):      @classmethod @@ -40,10 +41,24 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):          self.cli_delete(base_path)          self.cli_commit() +    def verify_nftables(self, nftables_search, table, inverse=False, args=''): +        nftables_output = cmd(f'sudo nft {args} list table {table}') + +        for search in nftables_search: +            matched = False +            for line in nftables_output.split("\n"): +                if all(item in line for item in search): +                    matched = True +                    break +            self.assertTrue(not matched if inverse else matched, msg=search) +      def test_snat(self):          rules = ['100', '110', '120', '130', '200', '210', '220', '230']          outbound_iface_100 = 'eth0'          outbound_iface_200 = 'eth1' + +        nftables_search = ['jump VYOS_PRE_SNAT_HOOK'] +          for rule in rules:              network = f'192.168.{rule}.0/24'              # depending of rule order we check either for source address for NAT @@ -52,51 +67,16 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):                  self.cli_set(src_path + ['rule', rule, 'source', 'address', network])                  self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_100])                  self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) +                nftables_search.append([f'saddr {network}', f'oifname "{outbound_iface_100}"', 'masquerade'])              else:                  self.cli_set(src_path + ['rule', rule, 'destination', 'address', network])                  self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface_200])                  self.cli_set(src_path + ['rule', rule, 'exclude']) +                nftables_search.append([f'daddr {network}', f'oifname "{outbound_iface_200}"', 'return'])          self.cli_commit() -        tmp = cmd('sudo nft -j list chain ip nat POSTROUTING') -        data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) - -        for idx in range(0, len(data_json)): -            data = data_json[idx] -            if idx == 0: -                self.assertEqual(data['chain'], 'POSTROUTING') -                self.assertEqual(data['family'], 'ip') -                self.assertEqual(data['table'], 'nat') - -                jump_target = dict_search('jump.target', data['expr'][1]) -                self.assertEqual(jump_target,'VYOS_PRE_SNAT_HOOK') -            else: -                rule = str(rules[idx - 1]) -                network = f'192.168.{rule}.0/24' - -                self.assertEqual(data['chain'], 'POSTROUTING') -                self.assertEqual(data['comment'], f'SRC-NAT-{rule}') -                self.assertEqual(data['family'], 'ip') -                self.assertEqual(data['table'], 'nat') - -                iface = dict_search('match.right', data['expr'][0]) -                direction = dict_search('match.left.payload.field', data['expr'][1]) -                address = dict_search('match.right.prefix.addr', data['expr'][1]) -                mask = dict_search('match.right.prefix.len', data['expr'][1]) - -                if int(rule) < 200: -                    self.assertEqual(direction, 'saddr') -                    self.assertEqual(iface, outbound_iface_100) -                    # check for masquerade keyword -                    self.assertIn('masquerade', data['expr'][3]) -                else: -                    self.assertEqual(direction, 'daddr') -                    self.assertEqual(iface, outbound_iface_200) -                    # check for return keyword due to 'exclude' -                    self.assertIn('return', data['expr'][3]) - -                self.assertEqual(f'{address}/{mask}', network) +        self.verify_nftables(nftables_search, 'ip vyos_nat')      def test_dnat(self):          rules = ['100', '110', '120', '130', '200', '210', '220', '230'] @@ -105,56 +85,29 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):          inbound_proto_100 = 'udp'          inbound_proto_200 = 'tcp' +        nftables_search = ['jump VYOS_PRE_DNAT_HOOK'] +          for rule in rules:              port = f'10{rule}'              self.cli_set(dst_path + ['rule', rule, 'source', 'port', port])              self.cli_set(dst_path + ['rule', rule, 'translation', 'address', '192.0.2.1'])              self.cli_set(dst_path + ['rule', rule, 'translation', 'port', port]) +            rule_search = [f'dnat to 192.0.2.1:{port}']              if int(rule) < 200:                  self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_100])                  self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_100]) +                rule_search.append(f'{inbound_proto_100} sport {port}') +                rule_search.append(f'iifname "{inbound_iface_100}"')              else:                  self.cli_set(dst_path + ['rule', rule, 'protocol', inbound_proto_200])                  self.cli_set(dst_path + ['rule', rule, 'inbound-interface', inbound_iface_200]) +                rule_search.append(f'iifname "{inbound_iface_200}"') -        self.cli_commit() - -        tmp = cmd('sudo nft -j list chain ip nat PREROUTING') -        data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) - -        for idx in range(0, len(data_json)): -            data = data_json[idx] -            if idx == 0: -                self.assertEqual(data['chain'], 'PREROUTING') -                self.assertEqual(data['family'], 'ip') -                self.assertEqual(data['table'], 'nat') +            nftables_search.append(rule_search) -                jump_target = dict_search('jump.target', data['expr'][1]) -                self.assertEqual(jump_target,'VYOS_PRE_DNAT_HOOK') -            else: +        self.cli_commit() -                rule = str(rules[idx - 1]) -                port = int(f'10{rule}') - -                self.assertEqual(data['chain'], 'PREROUTING') -                self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}') -                self.assertEqual(data['family'], 'ip') -                self.assertEqual(data['table'], 'nat') - -                iface = dict_search('match.right', data['expr'][0]) -                direction = dict_search('match.left.payload.field', data['expr'][1]) -                protocol = dict_search('match.left.payload.protocol', data['expr'][1]) -                dnat_addr = dict_search('dnat.addr', data['expr'][3]) -                dnat_port = dict_search('dnat.port', data['expr'][3]) - -                self.assertEqual(direction, 'sport') -                self.assertEqual(dnat_addr, '192.0.2.1') -                self.assertEqual(dnat_port, port) -                if int(rule) < 200: -                    self.assertEqual(iface, inbound_iface_100) -                    self.assertEqual(protocol, inbound_proto_100) -                else: -                    self.assertEqual(iface, inbound_iface_200) +        self.verify_nftables(nftables_search, 'ip vyos_nat')      def test_snat_required_translation_address(self):          # T2813: Ensure translation address is specified @@ -193,8 +146,48 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):          # without any rule          self.cli_set(src_path)          self.cli_set(dst_path) +        self.cli_set(static_path) +        self.cli_commit() + +    def test_dnat_without_translation_address(self): +        self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) +        self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) +        self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) +        self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443']) + +        self.cli_commit() + +        nftables_search = [ +            ['iifname "eth1"', 'tcp dport 443', 'dnat to :443'] +        ] + +        self.verify_nftables(nftables_search, 'ip vyos_nat') + +    def test_static_nat(self): +        dst_addr_1 = '10.0.1.1' +        translate_addr_1 = '192.168.1.1' +        dst_addr_2 = '203.0.113.0/24' +        translate_addr_2 = '192.0.2.0/24' +        ifname = 'eth0' + +        self.cli_set(static_path + ['rule', '10', 'destination', 'address', dst_addr_1]) +        self.cli_set(static_path + ['rule', '10', 'inbound-interface', ifname]) +        self.cli_set(static_path + ['rule', '10', 'translation', 'address', translate_addr_1]) + +        self.cli_set(static_path + ['rule', '20', 'destination', 'address', dst_addr_2]) +        self.cli_set(static_path + ['rule', '20', 'inbound-interface', ifname]) +        self.cli_set(static_path + ['rule', '20', 'translation', 'address', translate_addr_2]) +          self.cli_commit() +        nftables_search = [ +            [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'dnat to {translate_addr_1}'], +            [f'oifname "{ifname}"', f'ip saddr {translate_addr_1}', f'snat to {dst_addr_1}'], +            [f'iifname "{ifname}"', f'dnat ip prefix to ip daddr map {{ {dst_addr_2} : {translate_addr_2} }}'], +            [f'oifname "{ifname}"', f'snat ip prefix to ip saddr map {{ {translate_addr_2} : {dst_addr_2} }}'] +        ] + +        self.verify_nftables(nftables_search, 'ip vyos_static_nat')  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index 537b094a4..6cf7ca0a1 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -71,12 +71,12 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):          self.cli_commit()          nftables_search = [ -            ['oifname "eth1"', 'ip6 saddr fc00::/64', 'snat prefix to fc01::/64'], -            ['oifname "eth1"', 'ip6 saddr fc00::/64', 'masquerade'], -            ['oifname "eth1"', 'ip6 saddr fc00::/64', 'return'] +            ['oifname "eth1"', f'ip6 saddr {source_prefix}', f'snat prefix to {translation_prefix}'], +            ['oifname "eth1"', f'ip6 saddr {source_prefix}', 'masquerade'], +            ['oifname "eth1"', f'ip6 saddr {source_prefix}', 'return']          ] -        self.verify_nftables(nftables_search, 'ip6 nat') +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_source_nat66_address(self):          source_prefix = 'fc00::/64' @@ -88,25 +88,11 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):          # check validate() - outbound-interface must be defined          self.cli_commit() -        tmp = cmd('sudo nft -j list table ip6 nat') -        data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) - -        for idx in range(0, len(data_json)): -            data = data_json[idx] - -            self.assertEqual(data['chain'], 'POSTROUTING') -            self.assertEqual(data['family'], 'ip6') -            self.assertEqual(data['table'], 'nat') - -            iface = dict_search('match.right', data['expr'][0]) -            address = dict_search('match.right.prefix.addr', data['expr'][2]) -            mask = dict_search('match.right.prefix.len', data['expr'][2]) -            snat_address = dict_search('snat.addr', data['expr'][3]) +        nftables_search = [ +            ['oifname "eth1"', f'ip6 saddr {source_prefix}', f'snat to {translation_address}'] +        ] -            self.assertEqual(iface, 'eth1') -            # check for translation address -            self.assertEqual(snat_address, translation_address) -            self.assertEqual(f'{address}/{mask}', source_prefix) +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_destination_nat66(self):          destination_address = 'fc00::1' @@ -129,7 +115,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):              ['iifname "eth1"', 'ip6 saddr fc02::1', 'ip6 daddr fc00::1', 'return']          ] -        self.verify_nftables(nftables_search, 'ip6 nat') +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_destination_nat66_protocol(self):          translation_address = '2001:db8:1111::1' @@ -153,7 +139,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):              ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to 2001:db8:1111::1:5555']          ] -        self.verify_nftables(nftables_search, 'ip6 nat') +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_destination_nat66_prefix(self):          destination_prefix = 'fc00::/64' @@ -165,22 +151,25 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):          # check validate() - outbound-interface must be defined          self.cli_commit() -        tmp = cmd('sudo nft -j list table ip6 nat') -        data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp)) +        nftables_search = [ +            ['iifname "eth1"', f'ip6 daddr {destination_prefix}', f'dnat prefix to {translation_prefix}'] +        ] + +        self.verify_nftables(nftables_search, 'ip6 vyos_nat') -        for idx in range(0, len(data_json)): -            data = data_json[idx] +    def test_destination_nat66_without_translation_address(self): +        self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) +        self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) +        self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) +        self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443']) -            self.assertEqual(data['chain'], 'PREROUTING') -            self.assertEqual(data['family'], 'ip6') -            self.assertEqual(data['table'], 'nat') +        self.cli_commit() -            iface = dict_search('match.right', data['expr'][0]) -            translation_address = dict_search('dnat.addr.prefix.addr', data['expr'][3]) -            translation_mask = dict_search('dnat.addr.prefix.len', data['expr'][3]) +        nftables_search = [ +            ['iifname "eth1"', 'tcp dport 443', 'dnat to :443'] +        ] -            self.assertEqual(f'{translation_address}/{translation_mask}', translation_prefix) -            self.assertEqual(iface, 'eth1') +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_source_nat66_required_translation_prefix(self):          # T2813: Ensure translation address is specified @@ -222,7 +211,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):              ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to 2001:db8:1111::1:80']          ] -        self.verify_nftables(nftables_search, 'ip6 nat') +        self.verify_nftables(nftables_search, 'ip6 vyos_nat')      def test_nat66_no_rules(self):          # T3206: deleting all rules but keep the direction 'destination' or diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 6196ffe60..d2dad8c1a 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -287,6 +287,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):          self.cli_set(base_path + ['parameters', 'bestpath', 'as-path', 'multipath-relax'])          self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing'])          self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) +        self.cli_set(base_path + ['parameters', 'bestpath', 'peer-type', 'multipath-relax'])          self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer])          self.cli_set(base_path + ['parameters', 'fast-convergence']) @@ -318,6 +319,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):          self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig)          self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig)          self.assertIn(f' bgp bestpath compare-routerid', frrconfig) +        self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig)          self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig)          self.assertIn(f' bgp reject-as-sets', frrconfig)          self.assertIn(f' bgp shutdown', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py index 636caf950..59252875b 100755 --- a/smoketest/scripts/cli/test_protocols_nhrp.py +++ b/smoketest/scripts/cli/test_protocols_nhrp.py @@ -26,65 +26,79 @@ nhrp_path = ['protocols', 'nhrp']  vpn_path = ['vpn', 'ipsec']  class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase): +    @classmethod +    def setUpClass(cls): +        super(TestProtocolsNHRP, cls).setUpClass() + +        # ensure we can also run this test on a live system - so lets clean +        # out the current configuration :) +        cls.cli_delete(cls, nhrp_path) +        cls.cli_delete(cls, tunnel_path) +      def tearDown(self):          self.cli_delete(nhrp_path)          self.cli_delete(tunnel_path)          self.cli_commit()      def test_config(self): -        self.cli_delete(nhrp_path) -        self.cli_delete(tunnel_path) +        tunnel_if = "tun100" +        tunnel_source = "192.0.2.1" +        tunnel_encapsulation = "gre" +        esp_group = "ESP-HUB" +        ike_group = "IKE-HUB" +        nhrp_secret = "vyos123" +        nhrp_profile = "NHRPVPN" +        ipsec_secret = "secret"          # Tunnel -        self.cli_set(tunnel_path + ["tun100", "address", "172.16.253.134/29"]) -        self.cli_set(tunnel_path + ["tun100", "encapsulation", "gre"]) -        self.cli_set(tunnel_path + ["tun100", "source-address", "192.0.2.1"]) -        self.cli_set(tunnel_path + ["tun100", "multicast", "enable"]) -        self.cli_set(tunnel_path + ["tun100", "parameters", "ip", "key", "1"]) +        self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"]) +        self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation]) +        self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source]) +        self.cli_set(tunnel_path + [tunnel_if, "multicast", "enable"]) +        self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"])          # NHRP -        self.cli_set(nhrp_path + ["tunnel", "tun100", "cisco-authentication", "secret"]) -        self.cli_set(nhrp_path + ["tunnel", "tun100", "holding-time", "300"]) -        self.cli_set(nhrp_path + ["tunnel", "tun100", "multicast", "dynamic"]) -        self.cli_set(nhrp_path + ["tunnel", "tun100", "redirect"]) -        self.cli_set(nhrp_path + ["tunnel", "tun100", "shortcut"]) +        self.cli_set(nhrp_path + ["tunnel", tunnel_if, "cisco-authentication", nhrp_secret]) +        self.cli_set(nhrp_path + ["tunnel", tunnel_if, "holding-time", "300"]) +        self.cli_set(nhrp_path + ["tunnel", tunnel_if, "multicast", "dynamic"]) +        self.cli_set(nhrp_path + ["tunnel", tunnel_if, "redirect"]) +        self.cli_set(nhrp_path + ["tunnel", tunnel_if, "shortcut"])          # IKE/ESP Groups -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "compression", "disable"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "lifetime", "1800"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "mode", "transport"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "pfs", "dh-group2"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "encryption", "aes256"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "1", "hash", "sha1"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "encryption", "3des"]) -        self.cli_set(vpn_path + ["esp-group", "ESP-HUB", "proposal", "2", "hash", "md5"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "ikev2-reauth", "no"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "key-exchange", "ikev1"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "lifetime", "3600"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "dh-group", "2"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "encryption", "aes256"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "1", "hash", "sha1"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "dh-group", "2"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "encryption", "aes128"]) -        self.cli_set(vpn_path + ["ike-group", "IKE-HUB", "proposal", "2", "hash", "sha1"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "lifetime", "1800"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "mode", "transport"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "pfs", "dh-group2"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "encryption", "aes256"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "1", "hash", "sha1"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "encryption", "3des"]) +        self.cli_set(vpn_path + ["esp-group", esp_group, "proposal", "2", "hash", "md5"]) + +        self.cli_set(vpn_path + ["ike-group", ike_group, "key-exchange", "ikev1"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "lifetime", "3600"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "dh-group", "2"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "encryption", "aes256"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "1", "hash", "sha1"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "dh-group", "2"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "encryption", "aes128"]) +        self.cli_set(vpn_path + ["ike-group", ike_group, "proposal", "2", "hash", "sha1"])          # Profile - Not doing full DMVPN checks here, just want to verify the profile name in the output          self.cli_set(vpn_path + ["interface", "eth0"]) -        self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "mode", "pre-shared-secret"]) -        self.cli_set(vpn_path + ["profile", "NHRPVPN", "authentication", "pre-shared-secret", "secret"]) -        self.cli_set(vpn_path + ["profile", "NHRPVPN", "bind", "tunnel", "tun100"]) -        self.cli_set(vpn_path + ["profile", "NHRPVPN", "esp-group", "ESP-HUB"]) -        self.cli_set(vpn_path + ["profile", "NHRPVPN", "ike-group", "IKE-HUB"]) +        self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "mode", "pre-shared-secret"]) +        self.cli_set(vpn_path + ["profile", nhrp_profile, "authentication", "pre-shared-secret", ipsec_secret]) +        self.cli_set(vpn_path + ["profile", nhrp_profile, "bind", "tunnel", tunnel_if]) +        self.cli_set(vpn_path + ["profile", nhrp_profile, "esp-group", esp_group]) +        self.cli_set(vpn_path + ["profile", nhrp_profile, "ike-group", ike_group])          self.cli_commit()          opennhrp_lines = [ -            'interface tun100 #hub NHRPVPN', -            'cisco-authentication secret', -            'holding-time 300', -            'shortcut', -            'multicast dynamic', -            'redirect' +            f'interface {tunnel_if} #hub {nhrp_profile}', +            f'cisco-authentication {nhrp_secret}', +            f'holding-time 300', +            f'shortcut', +            f'multicast dynamic', +            f'redirect'          ]          tmp_opennhrp_conf = read_file('/run/opennhrp/opennhrp.conf') @@ -93,13 +107,13 @@ class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase):              self.assertIn(line, tmp_opennhrp_conf)          firewall_matches = [ -            'ip protocol gre', -            'ip saddr 192.0.2.1', -            'ip daddr 224.0.0.0/4', -            'comment "VYOS_NHRP_tun100"' +            f'ip protocol {tunnel_encapsulation}', +            f'ip saddr {tunnel_source}', +            f'ip daddr 224.0.0.0/4', +            f'comment "VYOS_NHRP_{tunnel_if}"'          ] -        self.assertTrue(find_nftables_rule('ip vyos_filter', 'VYOS_FW_OUTPUT', firewall_matches) is not None) +        self.assertTrue(find_nftables_rule('ip vyos_nhrp_filter', 'VYOS_NHRP_OUTPUT', firewall_matches) is not None)          self.assertTrue(process_named_running('opennhrp'))  if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py new file mode 100755 index 000000000..bdab35834 --- /dev/null +++ b/smoketest/scripts/cli/test_service_ipoe-server.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import re +import unittest + +from base_accel_ppp_test import BasicAccelPPPTest +from vyos.configsession import ConfigSessionError +from vyos.util import cmd + +from configparser import ConfigParser + +ac_name = 'ACN' +interface = 'eth0' + +class TestServiceIPoEServer(BasicAccelPPPTest.TestCase): +    @classmethod +    def setUpClass(cls): +        cls._base_path = ['service', 'ipoe-server'] +        cls._config_file = '/run/accel-pppd/ipoe.conf' +        cls._chap_secrets = '/run/accel-pppd/ipoe.chap-secrets' + +        # call base-classes classmethod +        super(TestServiceIPoEServer, cls).setUpClass() + +    def verify(self, conf): +        super().verify(conf) + +        # Validate configuration values +        accel_modules = list(conf['modules'].keys()) +        self.assertIn('log_syslog', accel_modules) +        self.assertIn('ipoe', accel_modules) +        self.assertIn('shaper', accel_modules) +        self.assertIn('ipv6pool', accel_modules) +        self.assertIn('ipv6_nd', accel_modules) +        self.assertIn('ipv6_dhcp', accel_modules) +        self.assertIn('ippool', accel_modules) + +    def basic_config(self): +        self.set(['interface', interface, 'client-subnet', '192.168.0.0/24']) + +    def test_accel_local_authentication(self): +        mac_address = '08:00:27:2f:d8:06' +        self.set(['authentication', 'interface', interface, 'mac', mac_address]) +        self.set(['authentication', 'mode', 'local']) + +        # No IPoE interface configured +        with self.assertRaises(ConfigSessionError): +            self.cli_commit() + +        # Test configuration of local authentication for PPPoE server +        self.basic_config() + +        # commit changes +        self.cli_commit() + +        # Validate configuration values +        conf = ConfigParser(allow_no_value=True, delimiters='=') +        conf.read(self._config_file) + +        # check proper path to chap-secrets file +        self.assertEqual(conf['chap-secrets']['chap-secrets'], self._chap_secrets) + +        accel_modules = list(conf['modules'].keys()) +        self.assertIn('chap-secrets', accel_modules) + +        # basic verification +        self.verify(conf) + +        # check local users +        tmp = cmd(f'sudo cat {self._chap_secrets}') +        regex = f'{interface}\s+\*\s+{mac_address}\s+\*' +        tmp = re.findall(regex, tmp) +        self.assertTrue(tmp) + +if __name__ == '__main__': +    unittest.main(verbosity=2) + diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 51cc098ef..7546c2e3d 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 020 VyOS maintainers and contributors +# Copyright (C) 2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -14,27 +14,27 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -import os  import unittest  from base_accel_ppp_test import BasicAccelPPPTest  from configparser import ConfigParser -from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running +from vyos.util import read_file +from vyos.template import range_to_regex  local_if = ['interfaces', 'dummy', 'dum667']  ac_name = 'ACN'  interface = 'eth0'  class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): -    def setUp(self): -        self._base_path = ['service', 'pppoe-server'] -        self._process_name = 'accel-pppd' -        self._config_file = '/run/accel-pppd/pppoe.conf' -        self._chap_secrets = '/run/accel-pppd/pppoe.chap-secrets' +    @classmethod +    def setUpClass(cls): +        cls._base_path = ['service', 'pppoe-server'] +        cls._config_file = '/run/accel-pppd/pppoe.conf' +        cls._chap_secrets = '/run/accel-pppd/pppoe.chap-secrets' -        super().setUp() +        # call base-classes classmethod +        super(TestServicePPPoEServer, cls).setUpClass()      def tearDown(self):          self.cli_delete(local_if) @@ -120,8 +120,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          # check interface-cache          self.assertEqual(conf['ppp']['unit-cache'], interface_cache) -        # Check for running process -        self.assertTrue(process_named_running(self._process_name))      def test_pppoe_server_authentication_protocols(self):          # Test configuration of local authentication for PPPoE server @@ -139,8 +137,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.assertEqual(conf['modules']['auth_mschap_v2'], None) -        # Check for running process -        self.assertTrue(process_named_running(self._process_name))      def test_pppoe_server_client_ip_pool(self):          # Test configuration of IPv6 client pools @@ -168,9 +164,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.assertEqual(conf['ip-pool'][start_stop], None)          self.assertEqual(conf['ip-pool']['gw-ip-address'], self._gateway) -        # Check for running process -        self.assertTrue(process_named_running(self._process_name)) -      def test_pppoe_server_client_ipv6_pool(self):          # Test configuration of IPv6 client pools @@ -211,9 +204,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.assertEqual(conf['ipv6-pool'][client_prefix], None)          self.assertEqual(conf['ipv6-pool']['delegate'], f'{delegate_prefix},{delegate_mask}') -        # Check for running process -        self.assertTrue(process_named_running(self._process_name)) -      def test_accel_radius_authentication(self):          radius_called_sid = 'ifname:mac' @@ -234,5 +224,27 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):          self.assertEqual(conf['radius']['acct-interim-jitter'], radius_acct_interim_jitter) +    def test_pppoe_server_vlan(self): + +        vlans = ['100', '200', '300-310'] + +        # Test configuration of local authentication for PPPoE server +        self.basic_config() + +        for vlan in vlans: +            self.set(['interface', interface, 'vlan', vlan]) + +        # commit changes +        self.cli_commit() + +        # Validate configuration values +        config = read_file(self._config_file) +        for vlan in vlans: +            tmp = range_to_regex(vlan) +            self.assertIn(f'interface=re:^{interface}\.{tmp}$', config) + +        tmp = ','.join(vlans) +        self.assertIn(f'vlan-mon={interface},{tmp}', config) +  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index 95c2a6c55..fd16146b1 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -59,7 +59,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):              },              'net.netfilter.nf_conntrack_tcp_max_retrans' :{                  'cli'           : ['tcp', 'max-retrans'], -                'test_value'    : '1024', +                'test_value'    : '128',                  'default_value' : '3',              },              'net.netfilter.nf_conntrack_icmp_timeout' :{ diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 8a6514d57..bd242104f 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -33,6 +33,7 @@ dhcp_waiting_file = '/tmp/ipsec_dhcp_waiting'  swanctl_file = '/etc/swanctl/swanctl.conf'  peer_ip = '203.0.113.45' +connection_name = 'main-branch'  interface = 'eth1'  vif = '100'  esp_group = 'MyESPGroup' @@ -150,7 +151,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server          # Site to site -        peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] +        peer_base_path = base_path + ['site-to-site', 'peer', connection_name]          self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])          self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])          self.cli_set(peer_base_path + ['ike-group', ike_group]) @@ -173,7 +174,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          priority = '20'          life_bytes = '100000'          life_packets = '2000000' -        peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] +        peer_base_path = base_path + ['site-to-site', 'peer', connection_name]          self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes])          self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets]) @@ -183,6 +184,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(peer_base_path + ['ike-group', ike_group])          self.cli_set(peer_base_path + ['default-esp-group', esp_group])          self.cli_set(peer_base_path + ['local-address', local_address]) +        self.cli_set(peer_base_path + ['remote-address', peer_ip])          self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'tcp'])          self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24'])          self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24']) @@ -211,11 +213,11 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):              f'local_addrs = {local_address} # dhcp:no',              f'remote_addrs = {peer_ip}',              f'mode = tunnel', -            f'peer_{peer_ip.replace(".","-")}_tunnel_1', +            f'{connection_name}-tunnel-1',              f'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]',              f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]',              f'mode = tunnel', -            f'peer_{peer_ip.replace(".","-")}_tunnel_2', +            f'{connection_name}-tunnel-2',              f'local_ts = 10.1.0.0/16',              f'remote_ts = 10.2.0.0/16',              f'priority = {priority}', @@ -226,7 +228,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          swanctl_secrets_lines = [              f'id-local = {local_address} # dhcp:no', -            f'id-remote = {peer_ip}', +            f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',              f'secret = "{secret}"'          ]          for line in swanctl_secrets_lines: @@ -236,18 +238,24 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):      def test_03_site_to_site_vti(self):          local_address = '192.0.2.10'          vti = 'vti10' +        # IKE +        self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) +        self.cli_set(base_path + ['ike-group', ike_group, 'disable-mobike']) +        # ESP +        self.cli_set(base_path + ['esp-group', esp_group, 'compression'])          # VTI interface          self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24']) -        self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2'])          # Site to site -        peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] +        peer_base_path = base_path + ['site-to-site', 'peer', connection_name]          self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])          self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])          self.cli_set(peer_base_path + ['connection-type', 'none']) +        self.cli_set(peer_base_path + ['force-udp-encapsulation'])          self.cli_set(peer_base_path + ['ike-group', ike_group])          self.cli_set(peer_base_path + ['default-esp-group', esp_group])          self.cli_set(peer_base_path + ['local-address', local_address]) +        self.cli_set(peer_base_path + ['remote-address', peer_ip])          self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24'])          self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24'])          self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']) @@ -269,10 +277,12 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):              f'proposals = aes128-sha1-modp1024',              f'esp_proposals = aes128-sha1-modp1024',              f'local_addrs = {local_address} # dhcp:no', +            f'mobike = no',              f'remote_addrs = {peer_ip}',              f'mode = tunnel',              f'local_ts = 172.16.10.0/24,172.16.11.0/24',              f'remote_ts = 172.17.10.0/24,172.17.11.0/24', +            f'ipcomp = yes',              f'start_action = none',              f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one              f'if_id_out = {if_id}', @@ -283,7 +293,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          swanctl_secrets_lines = [              f'id-local = {local_address} # dhcp:no', -            f'id-remote = {peer_ip}', +            f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',              f'secret = "{secret}"'          ]          for line in swanctl_secrets_lines: @@ -311,7 +321,6 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'shortcut'])          # IKE/ESP Groups -        self.cli_set(base_path + ['esp-group', esp_group, 'compression', 'disable'])          self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime])          self.cli_set(base_path + ['esp-group', esp_group, 'mode', 'transport'])          self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'dh-group2']) @@ -320,7 +329,6 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', '3des'])          self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'md5']) -        self.cli_set(base_path + ['ike-group', ike_group, 'ikev2-reauth', 'no'])          self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev1'])          self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime])          self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2']) @@ -366,10 +374,11 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(vti_path + [vti, 'address', '192.168.0.1/31'])          peer_ip = '172.18.254.202' +        connection_name = 'office'          local_address = '172.18.254.201' -        peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] +        peer_base_path = base_path + ['site-to-site', 'peer', connection_name] -        self.cli_set(peer_base_path + ['authentication', 'id', peer_name]) +        self.cli_set(peer_base_path + ['authentication', 'local-id', peer_name])          self.cli_set(peer_base_path + ['authentication', 'mode', 'x509'])          self.cli_set(peer_base_path + ['authentication', 'remote-id', 'peer2'])          self.cli_set(peer_base_path + ['authentication', 'x509', 'ca-certificate', ca_name]) @@ -378,6 +387,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(peer_base_path + ['ike-group', ike_group])          self.cli_set(peer_base_path + ['ikev2-reauth', 'inherit'])          self.cli_set(peer_base_path + ['local-address', local_address]) +        self.cli_set(peer_base_path + ['remote-address', peer_ip])          self.cli_set(peer_base_path + ['vti', 'bind', vti])          self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group]) @@ -391,7 +401,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          # to also support a vti0 interface          if_id = str(int(if_id) +1)          swanctl_lines = [ -            f'peer_{tmp}', +            f'{connection_name}',              f'version = 0', # key-exchange not set - defaulting to 0 for ikev1 and ikev2              f'send_cert = always',              f'mobike = yes', @@ -416,7 +426,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):              self.assertIn(line, swanctl_conf)          swanctl_secrets_lines = [ -            f'peer_{tmp}', +            f'{connection_name}',              f'file = {peer_name}.pem',          ]          for line in swanctl_secrets_lines: @@ -430,7 +440,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          local_address = '192.0.2.5'          local_id = 'vyos-r1'          remote_id = 'vyos-r2' -        peer_base_path = base_path + ['site-to-site', 'peer', peer_ip] +        peer_base_path = base_path + ['site-to-site', 'peer', connection_name]          self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre'])          self.cli_set(tunnel_path + ['tun1', 'source-address', local_address]) @@ -438,10 +448,9 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(base_path + ['interface', interface])          self.cli_set(base_path + ['options', 'flexvpn'])          self.cli_set(base_path + ['options', 'interface', 'tun1']) -        self.cli_set(base_path + ['ike-group', ike_group, 'ikev2-reauth', 'no'])          self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) -        self.cli_set(peer_base_path + ['authentication', 'id', local_id]) +        self.cli_set(peer_base_path + ['authentication', 'local-id', local_id])          self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret'])          self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret])          self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id]) @@ -449,6 +458,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          self.cli_set(peer_base_path + ['ike-group', ike_group])          self.cli_set(peer_base_path + ['default-esp-group', esp_group])          self.cli_set(peer_base_path + ['local-address', local_address]) +        self.cli_set(peer_base_path + ['remote-address', peer_ip])          self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre'])          self.cli_set(peer_base_path + ['virtual-address', '203.0.113.55']) @@ -464,7 +474,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):              f'life_time = 3600s', # default value              f'local_addrs = {local_address} # dhcp:no',              f'remote_addrs = {peer_ip}', -            f'peer_{peer_ip.replace(".","-")}_tunnel_1', +            f'{connection_name}-tunnel-1',              f'mode = tunnel',          ] @@ -473,7 +483,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):          swanctl_secrets_lines = [              f'id-local = {local_address} # dhcp:no', -            f'id-remote = {peer_ip}', +            f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}',              f'id-localid = {local_id}',              f'id-remoteid = {remote_id}',              f'secret = "{secret}"', diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py index f58920b5b..434e3aa05 100755 --- a/smoketest/scripts/cli/test_vpn_sstp.py +++ b/smoketest/scripts/cli/test_vpn_sstp.py @@ -19,29 +19,49 @@ import unittest  from base_accel_ppp_test import BasicAccelPPPTest  from vyos.util import read_file -  pki_path = ['pki'] -cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0=' -key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww' + +cert_data = """ +MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw +WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv +bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx +MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV +BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP +UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3 +QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu ++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz +ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93 ++dm/LDnp7C0=""" + +key_data = """ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx +2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7 +u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww +"""  class TestVPNSSTPServer(BasicAccelPPPTest.TestCase): -    def setUp(self): -        self._base_path = ['vpn', 'sstp'] -        self._process_name = 'accel-pppd' -        self._config_file = '/run/accel-pppd/sstp.conf' -        self._chap_secrets = '/run/accel-pppd/sstp.chap-secrets' -        super().setUp() +    @classmethod +    def setUpClass(cls): +        cls._base_path = ['vpn', 'sstp'] +        cls._config_file = '/run/accel-pppd/sstp.conf' +        cls._chap_secrets = '/run/accel-pppd/sstp.chap-secrets' -    def tearDown(self): -        self.cli_delete(pki_path) -        super().tearDown() +        # call base-classes classmethod +        super(TestVPNSSTPServer, cls).setUpClass() -    def basic_config(self): -        self.cli_delete(pki_path) -        self.cli_set(pki_path + ['ca', 'sstp', 'certificate', cert_data]) -        self.cli_set(pki_path + ['certificate', 'sstp', 'certificate', cert_data]) -        self.cli_set(pki_path + ['certificate', 'sstp', 'private', 'key', key_data]) +        cls.cli_set(cls, pki_path + ['ca', 'sstp', 'certificate', cert_data.replace('\n','')]) +        cls.cli_set(cls, pki_path + ['certificate', 'sstp', 'certificate', cert_data.replace('\n','')]) +        cls.cli_set(cls, pki_path + ['certificate', 'sstp', 'private', 'key', key_data.replace('\n','')]) +    @classmethod +    def tearDownClass(cls): +        cls.cli_delete(cls, pki_path) + +        super(TestVPNSSTPServer, cls).tearDownClass() + +    def basic_config(self):          # SSL is mandatory          self.set(['ssl', 'ca-certificate', 'sstp'])          self.set(['ssl', 'certificate', 'sstp']) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index eeb57bd30..cbd9cbe90 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -179,6 +179,20 @@ def verify_rule(firewall, rule_conf, ipv6):      if 'action' not in rule_conf:          raise ConfigError('Rule action must be defined') +    if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf: +        raise ConfigError('Action set to jump, but no jump-target specified') + +    if 'jump_target' in rule_conf: +        if 'jump' not in rule_conf['action']: +            raise ConfigError('jump-target defined, but action jump needed and it is not defined') +        target = rule_conf['jump_target'] +        if not ipv6: +            if target not in dict_search_args(firewall, 'name'): +                raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') +        else: +            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 'fragment' in rule_conf:          if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):              raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') @@ -287,6 +301,18 @@ def verify(firewall):      for name in ['name', 'ipv6_name']:          if name in firewall:              for name_id, name_conf in firewall[name].items(): +                if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: +                    raise ConfigError('default-action set to jump, but no default-jump-target specified') +                if 'default_jump_target' in name_conf: +                    target = name_conf['default_jump_target'] +                    if 'jump' not in name_conf['default_action']: +                        raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined') +                    if name_conf['default_jump_target'] == name_id: +                        raise ConfigError(f'Loop detected on default-jump-target.') +                    ## Now need to check that default-jump-target exists (other firewall chain/name) +                    if target not in dict_search_args(firewall, name): +                        raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system') +                  if 'rule' in name_conf:                      for rule_id, rule_conf in name_conf['rule'].items():                          verify_rule(firewall, rule_conf, name == 'ipv6_name') diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 61bab2feb..8d738f55e 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -14,16 +14,12 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -import os -  from sys import exit -from copy import deepcopy  from vyos.config import Config  from vyos.configdict import dict_merge  from vyos.configdict import get_interface_dict -from vyos.configdict import node_changed -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed  from vyos.configverify import verify_vrf  from vyos.configverify import verify_address  from vyos.configverify import verify_bridge_delete @@ -50,17 +46,20 @@ def get_config(config=None):      ifname, wireguard = get_interface_dict(conf, base)      # Check if a port was changed -    wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port']) +    tmp = is_node_changed(conf, base + [ifname, 'port']) +    if tmp: wireguard['port_changed'] = {}      # Determine which Wireguard peer has been removed.      # Peers can only be removed with their public key! -    dict = {} -    tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_')) -    for peer in (tmp or []): -        public_key = leaf_node_changed(conf, base + [ifname, 'peer', peer, 'public_key']) -        if public_key: -            dict = dict_merge({'peer_remove' : {peer : {'public_key' : public_key[0]}}}, dict) -            wireguard.update(dict) +    if 'peer' in wireguard: +        peer_remove = {} +        for peer, peer_config in wireguard['peer'].items(): +            # T4702: If anything on a peer changes we remove the peer first and re-add it +            if is_node_changed(conf, base + [ifname, 'peer', peer]): +                if 'public_key' in peer_config: +                    peer_remove = dict_merge({'peer_remove' : {peer : peer_config['public_key']}}, peer_remove) +        if peer_remove: +           wireguard.update(peer_remove)      return wireguard @@ -81,12 +80,11 @@ def verify(wireguard):      if 'peer' not in wireguard:          raise ConfigError('At least one Wireguard peer is required!') -    if 'port' in wireguard and wireguard['port_changed']: +    if 'port' in wireguard and 'port_changed' in wireguard:          listen_port = int(wireguard['port'])          if check_port_availability('0.0.0.0', listen_port, 'udp') is not True: -            raise ConfigError( -                f'The UDP port {listen_port} is busy or unavailable and cannot be used for the interface' -            ) +            raise ConfigError(f'UDP port {listen_port} is busy or unavailable and ' +                               'cannot be used for the interface!')      # run checks on individual configured WireGuard peer      for tmp in wireguard['peer']: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index e75418ba5..8b1a5a720 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -147,14 +147,10 @@ def verify(nat):                  Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')              addr = dict_search('translation.address', config) -            if addr != None: -                if addr != 'masquerade' and not is_ip_network(addr): -                    for ip in addr.split('-'): -                        if not is_addr_assigned(ip): -                            Warning(f'IP address {ip} does not exist on the system!') -            elif 'exclude' not in config: -                raise ConfigError(f'{err_msg}\n' \ -                                  'translation address not specified') +            if addr != None and addr != 'masquerade' and not is_ip_network(addr): +                for ip in addr.split('-'): +                    if not is_addr_assigned(ip): +                        Warning(f'IP address {ip} does not exist on the system!')              # common rule verification              verify_rule(config, err_msg) @@ -167,14 +163,8 @@ def verify(nat):              if 'inbound_interface' not in config:                  raise ConfigError(f'{err_msg}\n' \                                    'inbound-interface not specified') -            else: -                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 dict_search('translation.address', config) == None and 'exclude' not in config: -                raise ConfigError(f'{err_msg}\n' \ -                                  'translation address 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')              # common rule verification              verify_rule(config, err_msg) @@ -193,6 +183,9 @@ def verify(nat):      return None  def generate(nat): +    if not os.path.exists(nftables_nat_config): +        nat['first_install'] = True +      render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)      render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat) @@ -201,7 +194,9 @@ def generate(nat):      if tmp > 0:          raise ConfigError('Configuration file errors encountered!') -    tmp = run(f'nft -c -f {nftables_nat_config}') +    tmp = run(f'nft -c -f {nftables_static_nat_conf}') +    if tmp > 0: +        raise ConfigError('Configuration file errors encountered!')      return None diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index f64102d88..d8f913b0c 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -36,7 +36,7 @@ airbag.enable()  k_mod = ['nft_nat', 'nft_chain_nat'] -nftables_nat66_config = '/tmp/vyos-nat66-rules.nft' +nftables_nat66_config = '/run/nftables_nat66.nft'  ndppd_config = '/run/ndppd/ndppd.conf'  def get_handler(json, chain, target): @@ -147,6 +147,9 @@ def verify(nat):      return None  def generate(nat): +    if not os.path.exists(nftables_nat66_config): +        nat['first_install'] = True +      render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)      render(ndppd_config, 'ndppd/ndppd.conf.j2', nat, permission=0o755)      return None @@ -154,15 +157,15 @@ def generate(nat):  def apply(nat):      if not nat:          return None -    cmd(f'{nftables_nat66_config}') + +    cmd(f'nft -f {nftables_nat66_config}') +      if 'deleted' in nat or not dict_search('source.rule', nat):          cmd('systemctl stop ndppd')          if os.path.isfile(ndppd_config):              os.unlink(ndppd_config)      else:          cmd('systemctl restart ndppd') -    if os.path.isfile(nftables_nat66_config): -        os.unlink(nftables_nat66_config)      return None diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 9fddbd2c6..00539b9c7 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -92,7 +92,7 @@ def get_config(config=None):      return policy -def verify_rule(policy, name, rule_conf, ipv6): +def verify_rule(policy, name, rule_conf, ipv6, rule_id):      icmp = 'icmp' if not ipv6 else 'icmpv6'      if icmp in rule_conf:          icmp_defined = False @@ -166,7 +166,7 @@ def verify(policy):              for name, pol_conf in policy[route].items():                  if 'rule' in pol_conf:                      for rule_id, rule_conf in pol_conf['rule'].items(): -                        verify_rule(policy, name, rule_conf, ipv6) +                        verify_rule(policy, name, rule_conf, ipv6, rule_id)      for ifname, if_policy in policy['interfaces'].items():          name = dict_search_args(if_policy, 'route') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 5dafd26d0..cb8ea3be4 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -203,6 +203,28 @@ def verify(isis):          if list(set(global_range) & set(local_range)):              raise ConfigError(f'Segment-Routing Global Block ({g_low_label_value}/{g_high_label_value}) '\                                f'conflicts with Local Block ({l_low_label_value}/{l_high_label_value})!') +         +    # Check for a blank or invalid value per prefix +    if dict_search('segment_routing.prefix', isis): +        for prefix, prefix_config in isis['segment_routing']['prefix'].items(): +            if 'absolute' in prefix_config: +                if prefix_config['absolute'].get('value') is None: +                    raise ConfigError(f'Segment routing prefix {prefix} absolute value cannot be blank.') +            elif 'index' in prefix_config: +                if prefix_config['index'].get('value') is None: +                    raise ConfigError(f'Segment routing prefix {prefix} index value cannot be blank.') + +    # Check for explicit-null and no-php-flag configured at the same time per prefix +    if dict_search('segment_routing.prefix', isis): +        for prefix, prefix_config in isis['segment_routing']['prefix'].items(): +            if 'absolute' in prefix_config: +                if ("explicit_null" in prefix_config['absolute']) and ("no_php_flag" in prefix_config['absolute']):  +                    raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ +                                      f'and no-php-flag configured at the same time.') +            elif 'index' in prefix_config: +                if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']): +                    raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ +                                      f'and no-php-flag configured at the same time.')      return None diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 61f484129..e9afd6a55 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -15,266 +15,34 @@  # along with this program.  If not, see <http://www.gnu.org/licenses/>.  import os -import re -from copy import deepcopy -from stat import S_IRUSR, S_IWUSR, S_IRGRP  from sys import exit  from vyos.config import Config +from vyos.configdict import get_accel_dict +from vyos.configverify import verify_accel_ppp_base_service +from vyos.configverify import verify_interface_exists  from vyos.template import render -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 -from vyos.util import call, get_half_cpus +from vyos.util import call +from vyos.util import dict_search  from vyos import ConfigError -  from vyos import airbag  airbag.enable()  ipoe_conf = '/run/accel-pppd/ipoe.conf'  ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets' -default_config_data = { -    'auth_mode': 'local', -    'auth_interfaces': [], -    'chap_secrets_file': ipoe_chap_secrets, # used in Jinja2 template -    'interfaces': [], -    'dnsv4': [], -    'dnsv6': [], -    'client_named_ip_pool': [], -    'client_ipv6_pool': [], -    'client_ipv6_delegate_prefix': [], -    'radius_server': [], -    'radius_acct_inter_jitter': '', -    'radius_acct_tmo': '3', -    'radius_max_try': '3', -    'radius_timeout': '3', -    'radius_nas_id': '', -    'radius_nas_ip': '', -    'radius_source_address': '', -    'radius_shaper_attr': '', -    'radius_shaper_enable': False, -    'radius_shaper_multiplier': '', -    'radius_shaper_vendor': '', -    'radius_dynamic_author': '', -    'thread_cnt': get_half_cpus() -} -  def get_config(config=None):      if config:          conf = config      else:          conf = Config() -    base_path = ['service', 'ipoe-server'] -    if not conf.exists(base_path): +    base = ['service', 'ipoe-server'] +    if not conf.exists(base):          return None -    conf.set_level(base_path) -    ipoe = deepcopy(default_config_data) - -    for interface in conf.list_nodes(['interface']): -        tmp  = { -            'mode': 'L2', -            'name': interface, -            'shared': '1', -            # may need a config option, can be dhcpv4 or up for unclassified pkts -            'sess_start': 'dhcpv4', -            'range': None, -            'ifcfg': '1', -            'vlan_mon': [] -        } - -        conf.set_level(base_path + ['interface', interface]) - -        if conf.exists(['network-mode']): -            tmp['mode'] = conf.return_value(['network-mode']) - -        if conf.exists(['network']): -            mode = conf.return_value(['network']) -            if mode == 'vlan': -                tmp['shared'] = '0' - -                if conf.exists(['vlan-id']): -                    tmp['vlan_mon'] += conf.return_values(['vlan-id']) - -                if conf.exists(['vlan-range']): -                    tmp['vlan_mon'] += conf.return_values(['vlan-range']) - -        if conf.exists(['client-subnet']): -            tmp['range'] = conf.return_value(['client-subnet']) - -        ipoe['interfaces'].append(tmp) - -    conf.set_level(base_path) - -    if conf.exists(['name-server']): -        for name_server in conf.return_values(['name-server']): -            if is_ipv4(name_server): -                ipoe['dnsv4'].append(name_server) -            else: -                ipoe['dnsv6'].append(name_server) - -    if conf.exists(['authentication', 'mode']): -        ipoe['auth_mode'] = conf.return_value(['authentication', 'mode']) - -    if conf.exists(['authentication', 'interface']): -        for interface in conf.list_nodes(['authentication', 'interface']): -            tmp = { -                'name': interface, -                'mac': [] -            } -            for mac in conf.list_nodes(['authentication', 'interface', interface, 'mac-address']): -                client = { -                    'address': mac, -                    'rate_download': '', -                    'rate_upload': '', -                    'vlan_id': '' -                } -                conf.set_level(base_path + ['authentication', 'interface', interface, 'mac-address', mac]) - -                if conf.exists(['rate-limit', 'download']): -                    client['rate_download'] = conf.return_value(['rate-limit', 'download']) - -                if conf.exists(['rate-limit', 'upload']): -                    client['rate_upload'] = conf.return_value(['rate-limit', 'upload']) - -                if conf.exists(['vlan-id']): -                    client['vlan'] = conf.return_value(['vlan-id']) - -                tmp['mac'].append(client) - -            ipoe['auth_interfaces'].append(tmp) - -    conf.set_level(base_path) - -    # -    # authentication mode radius servers and settings -    if conf.exists(['authentication', 'mode', 'radius']): -        for server in conf.list_nodes(['authentication', 'radius', 'server']): -            radius = { -                'server' : server, -                'key' : '', -                'fail_time' : 0, -                'port' : '1812', -                'acct_port' : '1813' -            } - -            conf.set_level(base_path + ['authentication', 'radius', 'server', server]) - -            if conf.exists(['fail-time']): -                radius['fail_time'] = conf.return_value(['fail-time']) - -            if conf.exists(['port']): -                radius['port'] = conf.return_value(['port']) - -            if conf.exists(['acct-port']): -                radius['acct_port'] = conf.return_value(['acct-port']) - -            if conf.exists(['key']): -                radius['key'] = conf.return_value(['key']) - -            if not conf.exists(['disable']): -                ipoe['radius_server'].append(radius) - -    # -    # advanced radius-setting -    conf.set_level(base_path + ['authentication', 'radius']) - -    if conf.exists(['acct-interim-jitter']): -        ipoe['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter']) - -    if conf.exists(['acct-timeout']): -        ipoe['radius_acct_tmo'] = conf.return_value(['acct-timeout']) - -    if conf.exists(['max-try']): -        ipoe['radius_max_try'] = conf.return_value(['max-try']) - -    if conf.exists(['timeout']): -        ipoe['radius_timeout'] = conf.return_value(['timeout']) - -    if conf.exists(['nas-identifier']): -        ipoe['radius_nas_id'] = conf.return_value(['nas-identifier']) - -    if conf.exists(['nas-ip-address']): -        ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address']) - -    if conf.exists(['rate-limit', 'attribute']): -        ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute']) - -    if conf.exists(['rate-limit', 'enable']): -        ipoe['radius_shaper_enable'] = True - -    if conf.exists(['rate-limit', 'multiplier']): -        ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier']) - -    if conf.exists(['rate-limit', 'vendor']): -        ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor']) - -    if conf.exists(['source-address']): -        ipoe['radius_source_address'] = conf.return_value(['source-address']) - -    # Dynamic Authorization Extensions (DOA)/Change Of Authentication (COA) -    if conf.exists(['dynamic-author']): -        dae = { -            'port' : '', -            'server' : '', -            'key' : '' -        } - -        if conf.exists(['dynamic-author', 'server']): -            dae['server'] = conf.return_value(['dynamic-author', 'server']) - -        if conf.exists(['dynamic-author', 'port']): -            dae['port'] = conf.return_value(['dynamic-author', 'port']) - -        if conf.exists(['dynamic-author', 'key']): -            dae['key'] = conf.return_value(['dynamic-author', 'key']) - -        ipoe['radius_dynamic_author'] = dae - - -    conf.set_level(base_path) -    # Named client-ip-pool -    if conf.exists(['client-ip-pool', 'name']): -        for name in conf.list_nodes(['client-ip-pool', 'name']): -            tmp = { -                'name': name, -                'gateway_address': '', -                'subnet': '' -            } - -            if conf.exists(['client-ip-pool', 'name', name, 'gateway-address']): -                tmp['gateway_address'] += conf.return_value(['client-ip-pool', 'name', name, 'gateway-address']) -            if conf.exists(['client-ip-pool', 'name', name, 'subnet']): -                tmp['subnet'] += conf.return_value(['client-ip-pool', 'name', name, 'subnet']) - -            ipoe['client_named_ip_pool'].append(tmp) - -    if conf.exists(['client-ipv6-pool', 'prefix']): -        for prefix in conf.list_nodes(['client-ipv6-pool', 'prefix']): -            tmp = { -                'prefix': prefix, -                'mask': '64' -            } - -            if conf.exists(['client-ipv6-pool', 'prefix', prefix, 'mask']): -                tmp['mask'] = conf.return_value(['client-ipv6-pool', 'prefix', prefix, 'mask']) - -            ipoe['client_ipv6_pool'].append(tmp) - - -    if conf.exists(['client-ipv6-pool', 'delegate']): -        for prefix in conf.list_nodes(['client-ipv6-pool', 'delegate']): -            tmp = { -                'prefix': prefix, -                'mask': '' -            } - -            if conf.exists(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix']): -                tmp['mask'] = conf.return_value(['client-ipv6-pool', 'delegate', prefix, 'delegation-prefix']) - -            ipoe['client_ipv6_delegate_prefix'].append(tmp) - +    # retrieve common dictionary keys +    ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)      return ipoe @@ -282,26 +50,17 @@ def verify(ipoe):      if not ipoe:          return None -    if not ipoe['interfaces']: +    if 'interface' not in ipoe:          raise ConfigError('No IPoE interface configured') -    if len(ipoe['dnsv4']) > 2: -        raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') - -    if len(ipoe['dnsv6']) > 3: -        raise ConfigError('Not more then three IPv6 DNS name-servers can be configured') - -    if ipoe['auth_mode'] == 'radius': -        if len(ipoe['radius_server']) == 0: -            raise ConfigError('RADIUS authentication requires at least one server') +    for interface in ipoe['interface']: +        verify_interface_exists(interface) -        for radius in ipoe['radius_server']: -            if not radius['key']: -                server = radius['server'] -                raise ConfigError(f'Missing RADIUS secret key for server "{ server }"') +    #verify_accel_ppp_base_service(ipoe, local_users=False) -    if ipoe['client_ipv6_delegate_prefix'] and not ipoe['client_ipv6_pool']: -        raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!') +    if 'client_ipv6_pool' in ipoe: +        if 'delegate' in ipoe['client_ipv6_pool'] and 'prefix' not in ipoe['client_ipv6_pool']: +            raise ConfigError('IPoE IPv6 deletate-prefix requires IPv6 prefix to be configured!')      return None @@ -312,27 +71,23 @@ def generate(ipoe):      render(ipoe_conf, 'accel-ppp/ipoe.config.j2', ipoe) -    if ipoe['auth_mode'] == 'local': -        render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', ipoe) -        os.chmod(ipoe_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) - -    else: -        if os.path.exists(ipoe_chap_secrets): -             os.unlink(ipoe_chap_secrets) - +    if dict_search('authentication.mode', ipoe) == 'local': +        render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.j2', +               ipoe, permission=0o640)      return None  def apply(ipoe): +    systemd_service = 'accel-ppp@ipoe.service'      if ipoe == None: -        call('systemctl stop accel-ppp@ipoe.service') +        call(f'systemctl stop {systemd_service}')          for file in [ipoe_conf, ipoe_chap_secrets]:              if os.path.exists(file):                  os.unlink(file)          return None -    call('systemctl restart accel-ppp@ipoe.service') +    call(f'systemctl reload-or-restart {systemd_service}')  if __name__ == '__main__':      try: diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 6086ef859..ba0249efd 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -21,14 +21,12 @@ from sys import exit  from vyos.config import Config  from vyos.configdict import get_accel_dict  from vyos.configverify import verify_accel_ppp_base_service +from vyos.configverify import verify_interface_exists  from vyos.template import render  from vyos.util import call  from vyos.util import dict_search -from vyos.util import get_interface_config  from vyos import ConfigError  from vyos import airbag -from vyos.range_regex import range_to_regex -  airbag.enable()  pppoe_conf = r'/run/accel-pppd/pppoe.conf' @@ -54,15 +52,14 @@ def verify(pppoe):      verify_accel_ppp_base_service(pppoe)      if 'wins_server' in pppoe and len(pppoe['wins_server']) > 2: -        raise ConfigError('Not more then two IPv4 WINS name-servers can be configured') +        raise ConfigError('Not more then two WINS name-servers can be configured')      if 'interface' not in pppoe:          raise ConfigError('At least one listen interface must be defined!')      # Check is interface exists in the system -    for iface in pppoe['interface']: -        if not get_interface_config(iface): -            raise ConfigError(f'Interface {iface} does not exist!') +    for interface in pppoe['interface']: +        verify_interface_exists(interface)      # local ippool and gateway settings config checks      if not (dict_search('client_ip_pool.subnet', pppoe) or @@ -81,35 +78,24 @@ def generate(pppoe):      if not pppoe:          return None -    # Generate special regex for dynamic interfaces -    for iface in pppoe['interface']: -        if 'vlan_range' in pppoe['interface'][iface]: -            pppoe['interface'][iface]['regex'] = [] -            for vlan_range in pppoe['interface'][iface]['vlan_range']: -                pppoe['interface'][iface]['regex'].append(range_to_regex(vlan_range)) -      render(pppoe_conf, 'accel-ppp/pppoe.config.j2', pppoe)      if dict_search('authentication.mode', pppoe) == 'local':          render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.j2',                 pppoe, permission=0o640) -    else: -        if os.path.exists(pppoe_chap_secrets): -            os.unlink(pppoe_chap_secrets) -      return None  def apply(pppoe): +    systemd_service = 'accel-ppp@pppoe.service'      if not pppoe: -        call('systemctl stop accel-ppp@pppoe.service') +        call(f'systemctl stop {systemd_service}')          for file in [pppoe_conf, pppoe_chap_secrets]:              if os.path.exists(file):                  os.unlink(file) -          return None -    call('systemctl restart accel-ppp@pppoe.service') +    call(f'systemctl reload-or-restart {systemd_service}')  if __name__ == '__main__':      try: diff --git a/src/conf_mode/system_update_check.py b/src/conf_mode/system_update_check.py new file mode 100755 index 000000000..08ecfcb81 --- /dev/null +++ b/src/conf_mode/system_update_check.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import os +import json +import jmespath + +from pathlib import Path +from sys import exit + +from vyos.config import Config +from vyos.util import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +base = ['system', 'update-check'] +service_name = 'vyos-system-update' +service_conf = Path(f'/run/{service_name}.conf') +motd_file = Path('/run/motd.d/10-vyos-update') + + +def get_config(config=None): +    if config: +        conf = config +    else: +        conf = Config() + +    if not conf.exists(base): +        return None + +    config = conf.get_config_dict(base, key_mangling=('-', '_'), +                                  get_first_key=True, no_tag_node_value_mangle=True) + +    return config + + +def verify(config): +    # bail out early - looks like removal from running config +    if config is None: +        return + +    if 'url' not in config: +        raise ConfigError('URL is required!') + + +def generate(config): +    # bail out early - looks like removal from running config +    if config is None: +        # Remove old config and return +        service_conf.unlink(missing_ok=True) +        # MOTD used in /run/motd.d/10-update +        motd_file.unlink(missing_ok=True) +        return None + +    # Write configuration file +    conf_json = json.dumps(config, indent=4) +    service_conf.write_text(conf_json) + +    return None + + +def apply(config): +    if config: +        if 'auto_check' in config: +            call(f'systemctl restart {service_name}.service') +    else: +        call(f'systemctl stop {service_name}.service') + + +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/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 5ca32d23e..c9061366d 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -16,6 +16,7 @@  import ipaddress  import os +import re  from sys import exit  from time import sleep @@ -348,6 +349,14 @@ def verify(ipsec):      if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']:          for peer, peer_conf in ipsec['site_to_site']['peer'].items():              has_default_esp = False +            # Peer name it is swanctl connection name and shouldn't contain dots or colons, T4118 +            if bool(re.search(':|\.', peer)): +                raise ConfigError(f'Incorrect peer name "{peer}" ' +                                  f'Peer name can contain alpha-numeric letters, hyphen and underscore') + +            if 'remote_address' not in peer_conf: +                print(f'You should set correct remote-address "peer {peer} remote-address x.x.x.x"\n') +              if 'default_esp_group' in peer_conf:                  has_default_esp = True                  if 'esp_group' not in ipsec or peer_conf['default_esp_group'] not in ipsec['esp_group']: diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index 240546817..c050b796b 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -58,15 +58,16 @@ def get_config():      default_values = defaults(base)      ocserv = dict_merge(default_values, ocserv) -    # workaround a "know limitation" - https://phabricator.vyos.net/T2665 -    del ocserv['authentication']['local_users']['username']['otp'] -    if not ocserv["authentication"]["local_users"]["username"]: -        raise ConfigError('openconnect mode local required at least one user') -    default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp'] -    for user, params in ocserv['authentication']['local_users']['username'].items(): -        # Not every configuration requires OTP settings -        if ocserv['authentication']['local_users']['username'][user].get('otp'): -            ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp']) +    if "local" in ocserv["authentication"]["mode"]: +        # workaround a "know limitation" - https://phabricator.vyos.net/T2665 +        del ocserv['authentication']['local_users']['username']['otp'] +        if not ocserv["authentication"]["local_users"]["username"]: +            raise ConfigError('openconnect mode local required at least one user') +        default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp'] +        for user, params in ocserv['authentication']['local_users']['username'].items(): +            # Not every configuration requires OTP settings +            if ocserv['authentication']['local_users']['username'][user].get('otp'): +                ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp'])      if ocserv:          ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), @@ -80,9 +81,10 @@ def verify(ocserv):      # Check if listen-ports not binded other services      # It can be only listen by 'ocserv-main'      for proto, port in ocserv.get('listen_ports').items(): -        if check_port_availability('0.0.0.0', int(port), proto) is not True and \ +        if check_port_availability(ocserv['listen_address'], int(port), proto) is not True and \                  not is_listen_port_bind_service(int(port), 'ocserv-main'):              raise ConfigError(f'"{proto}" port "{port}" is used by another service') +      # Check authentication      if "authentication" in ocserv:          if "mode" in ocserv["authentication"]: diff --git a/src/migration-scripts/ipoe-server/0-to-1 b/src/migration-scripts/ipoe-server/0-to-1 index f328ebced..d768758ba 100755 --- a/src/migration-scripts/ipoe-server/0-to-1 +++ b/src/migration-scripts/ipoe-server/0-to-1 @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2022 VyOS maintainers and contributors  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License version 2 or later as @@ -14,8 +14,11 @@  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -# - remove primary/secondary identifier from nameserver -# - Unifi RADIUS configuration by placing it all under "authentication radius" node +# - T4703: merge vlan-id and vlan-range to vlan CLI node + +# L2|L3 -> l2|l3 +# mac-address -> mac +# network-mode -> mode  import os  import sys @@ -37,97 +40,35 @@ base = ['service', 'ipoe-server']  if not config.exists(base):      # Nothing to do      exit(0) -else: - -    # Migrate IPv4 DNS servers -    dns_base = base + ['dns-server'] -    if config.exists(dns_base): -        for server in ['server-1', 'server-2']: -          if config.exists(dns_base + [server]): -            dns = config.return_value(dns_base + [server]) -            config.set(base + ['name-server'], value=dns, replace=False) - -        config.delete(dns_base) - -    # Migrate IPv6 DNS servers -    dns_base = base + ['dnsv6-server'] -    if config.exists(dns_base): -        for server in ['server-1', 'server-2', 'server-3']: -          if config.exists(dns_base + [server]): -            dns = config.return_value(dns_base + [server]) -            config.set(base + ['name-server'], value=dns, replace=False) - -        config.delete(dns_base) - -    # Migrate radius-settings node to RADIUS and use this as base for the -    # later migration of the RADIUS servers - this will save a lot of code -    radius_settings = base + ['authentication', 'radius-settings'] -    if config.exists(radius_settings): -        config.rename(radius_settings, 'radius') - -    # Migrate RADIUS dynamic author / change of authorisation server -    dae_old = base + ['authentication', 'radius', 'dae-server'] -    if config.exists(dae_old): -        config.rename(dae_old, 'dynamic-author') -        dae_new = base + ['authentication', 'radius', 'dynamic-author'] - -        if config.exists(dae_new + ['ip-address']): -            config.rename(dae_new + ['ip-address'], 'server') - -        if config.exists(dae_new + ['secret']): -            config.rename(dae_new + ['secret'], 'key') -    # Migrate RADIUS server -    radius_server = base + ['authentication', 'radius-server'] -    if config.exists(radius_server): -        new_base = base + ['authentication', 'radius', 'server'] -        config.set(new_base) -        config.set_tag(new_base) -        for server in config.list_nodes(radius_server): -            old_base = radius_server + [server] -            config.copy(old_base, new_base + [server]) - -            # migrate key -            if config.exists(new_base + [server, 'secret']): -                config.rename(new_base + [server, 'secret'], 'key') - -            # remove old req-limit node -            if config.exists(new_base + [server, 'req-limit']): -                config.delete(new_base + [server, 'req-limit']) - -        config.delete(radius_server) - -    # Migrate IPv6 prefixes -    ipv6_base = base + ['client-ipv6-pool'] -    if config.exists(ipv6_base + ['prefix']): -        prefix_old = config.return_values(ipv6_base + ['prefix']) -        # delete old prefix CLI nodes -        config.delete(ipv6_base + ['prefix']) -        # create ned prefix tag node -        config.set(ipv6_base + ['prefix']) -        config.set_tag(ipv6_base + ['prefix']) - -        for p in prefix_old: -            prefix = p.split(',')[0] -            mask = p.split(',')[1] -            config.set(ipv6_base + ['prefix', prefix, 'mask'], value=mask) - -    if config.exists(ipv6_base + ['delegate-prefix']): -        prefix_old = config.return_values(ipv6_base + ['delegate-prefix']) -        # delete old delegate prefix CLI nodes -        config.delete(ipv6_base + ['delegate-prefix']) -        # create ned delegation tag node -        config.set(ipv6_base + ['delegate']) -        config.set_tag(ipv6_base + ['delegate']) - -        for p in prefix_old: -            prefix = p.split(',')[0] -            mask = p.split(',')[1] -            config.set(ipv6_base + ['delegate', prefix, 'delegation-prefix'], value=mask) - -    try: -        with open(file_name, 'w') as f: -            f.write(config.to_string()) -    except OSError as e: -        print("Failed to save the modified config: {}".format(e)) -        exit(1) +if config.exists(base + ['authentication', 'interface']): +    for interface in config.list_nodes(base + ['authentication', 'interface']): +        config.rename(base + ['authentication', 'interface', interface, 'mac-address'], 'mac') + +        mac_base = base + ['authentication', 'interface', interface, 'mac'] +        for mac in config.list_nodes(mac_base): +            vlan_config = mac_base + [mac, 'vlan-id'] +            if config.exists(vlan_config): +                config.rename(vlan_config, 'vlan') + +for interface in config.list_nodes(base + ['interface']): +    base_path = base + ['interface', interface] +    for vlan in ['vlan-id', 'vlan-range']: +        if config.exists(base_path + [vlan]): +            print(interface, vlan) +            for tmp in config.return_values(base_path + [vlan]): +                config.set(base_path + ['vlan'], value=tmp, replace=False) +            config.delete(base_path + [vlan]) + +    if config.exists(base_path + ['network-mode']): +        tmp = config.return_value(base_path + ['network-mode']) +        config.delete(base_path + ['network-mode']) +        # Change L2|L3 to lower case l2|l3 +        config.set(base_path + ['mode'], value=tmp.lower()) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10 new file mode 100755 index 000000000..1254104cb --- /dev/null +++ b/src/migration-scripts/ipsec/9-to-10 @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 + + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['vpn', 'ipsec'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +# IKE changes, T4118: +if config.exists(base + ['ike-group']): +    for ike_group in config.list_nodes(base + ['ike-group']): +        # replace 'ipsec ike-group <tag> mobike disable' +        #      => 'ipsec ike-group <tag> disable-mobike' +        mobike = base + ['ike-group', ike_group, 'mobike'] +        if config.exists(mobike): +            if config.return_value(mobike) == 'disable': +                config.set(base + ['ike-group', ike_group, 'disable-mobike']) +            config.delete(mobike) + +        # replace 'ipsec ike-group <tag> ikev2-reauth yes' +        #      => 'ipsec ike-group <tag> ikev2-reauth' +        reauth = base + ['ike-group', ike_group, 'ikev2-reauth'] +        if config.exists(reauth): +            if config.return_value(reauth) == 'yes': +                config.delete(reauth) +                config.set(reauth) +            else: +                config.delete(reauth) + +# ESP changes +# replace 'ipsec esp-group <tag> compression enable' +#      => 'ipsec esp-group <tag> compression' +if config.exists(base + ['esp-group']): +    for esp_group in config.list_nodes(base + ['esp-group']): +        compression = base + ['esp-group', esp_group, 'compression'] +        if config.exists(compression): +            if config.return_value(compression) == 'enable': +                config.delete(compression) +                config.set(compression) +            else: +                config.delete(compression) + +# PEER changes +if config.exists(base + ['site-to-site', 'peer']): +    for peer in config.list_nodes(base + ['site-to-site', 'peer']): +        peer_base = base + ['site-to-site', 'peer', peer] + +        # replace: 'peer <tag> id x' +        #       => 'peer <tag> local-id x' +        if config.exists(peer_base + ['authentication', 'id']): +            config.rename(peer_base + ['authentication', 'id'], 'local-id') + +        # For the peer '@foo' set remote-id 'foo' if remote-id is not defined +        if peer.startswith('@'): +            if not config.exists(peer_base + ['authentication', 'remote-id']): +                tmp = peer.replace('@', '') +                config.set(peer_base + ['authentication', 'remote-id'], value=tmp) + +        # replace: 'peer <tag> force-encapsulation enable' +        #       => 'peer <tag> force-udp-encapsulation' +        force_enc = peer_base + ['force-encapsulation'] +        if config.exists(force_enc): +            if config.return_value(force_enc) == 'enable': +                config.delete(force_enc) +                config.set(peer_base + ['force-udp-encapsulation']) +            else: +                config.delete(force_enc) + +        # add option: 'peer <tag> remote-address x.x.x.x' +        remote_address = peer +        if peer.startswith('@'): +            remote_address = 'any' +        config.set(peer_base + ['remote-address'], value=remote_address) +        # Peer name it is swanctl connection name and shouldn't contain dots or colons +        # rename peer: +        #   peer 192.0.2.1   => peer peer_192-0-2-1 +        #   peer 2001:db8::2 => peer peer_2001-db8--2 +        #   peer @foo        => peer peer_foo +        re_peer_name = re.sub(':|\.', '-', peer) +        if re_peer_name.startswith('@'): +            re_peer_name = re.sub('@', '', re_peer_name) +        new_peer_name = f'peer_{re_peer_name}' + +        config.rename(peer_base, new_peer_name) + +# remote-access/road-warrior changes +if config.exists(base + ['remote-access', 'connection']): +    for connection in config.list_nodes(base + ['remote-access', 'connection']): +        ra_base = base + ['remote-access', 'connection', connection] +        # replace: 'remote-access connection <tag> authentication id x' +        #       => 'remote-access connection <tag> authentication local-id x' +        if config.exists(ra_base + ['authentication', 'id']): +            config.rename(ra_base + ['authentication', 'id'], 'local-id') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/pppoe-server/5-to-6 b/src/migration-scripts/pppoe-server/5-to-6 new file mode 100755 index 000000000..e4888f4db --- /dev/null +++ b/src/migration-scripts/pppoe-server/5-to-6 @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# - T4703: merge vlan-id and vlan-range to vlan CLI node + +from vyos.configtree import ConfigTree +from sys import argv +from sys import exit + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base_path = ['service', 'pppoe-server', 'interface'] +if not config.exists(base_path): +    # Nothing to do +    exit(0) + +for interface in config.list_nodes(base_path): +    for vlan in ['vlan-id', 'vlan-range']: +        if config.exists(base_path + [interface, vlan]): +            print(interface, vlan) +            for tmp in config.return_values(base_path + [interface, vlan]): +                config.set(base_path + [interface, 'vlan'], value=tmp, replace=False) +            config.delete(base_path + [interface, vlan]) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) + diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 1339d5b92..a0496dedb 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -60,7 +60,7 @@ def _get_json_data(direction, family):      if direction == 'destination':          chain = 'PREROUTING'      family = 'ip6' if family == 'inet6' else 'ip' -    return cmd(f'sudo nft --json list chain {family} nat {chain}') +    return cmd(f'sudo nft --json list chain {family} vyos_nat {chain}')  def _get_raw_data_rules(direction, family): diff --git a/src/op_mode/show_nat66_statistics.py b/src/op_mode/show_nat66_statistics.py index bc81692ae..cb10aed9f 100755 --- a/src/op_mode/show_nat66_statistics.py +++ b/src/op_mode/show_nat66_statistics.py @@ -44,7 +44,7 @@ group.add_argument("--destination", help="Show statistics for configured destina  args = parser.parse_args()  if args.source or args.destination: -    tmp = cmd('sudo nft -j list table ip6 nat') +    tmp = cmd('sudo nft -j list table ip6 vyos_nat')      tmp = json.loads(tmp)      source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" diff --git a/src/op_mode/show_nat_statistics.py b/src/op_mode/show_nat_statistics.py index c568c8305..be41e083b 100755 --- a/src/op_mode/show_nat_statistics.py +++ b/src/op_mode/show_nat_statistics.py @@ -44,7 +44,7 @@ group.add_argument("--destination", help="Show statistics for configured destina  args = parser.parse_args()  if args.source or args.destination: -    tmp = cmd('sudo nft -j list table ip nat') +    tmp = cmd('sudo nft -j list table ip vyos_nat')      tmp = json.loads(tmp)      source = r"nftables[?rule.chain=='POSTROUTING'].rule.{chain: chain, handle: handle, comment: comment, counter: expr[].counter | [0], interface: expr[].match.right | [0] }" diff --git a/src/op_mode/system.py b/src/op_mode/system.py new file mode 100755 index 000000000..11a3a8730 --- /dev/null +++ b/src/op_mode/system.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import json +import sys +import requests +import typing + +from sys import exit + +from vyos.configquery import ConfigTreeQuery + +import vyos.opmode +import vyos.version + +config = ConfigTreeQuery() +base = ['system', 'update-check'] + + +def _compare_version_raw(): +    url = config.value(base + ['url']) +    local_data = vyos.version.get_full_version_data() +    remote_data = vyos.version.get_remote_version(url) +    if not remote_data: +        return {"error": True, +                "reason": "Unable to get remote version"} +    if local_data.get('version') and remote_data: +        local_version = local_data.get('version') +        remote_version = jmespath.search('[0].version', remote_data) +        image_url = jmespath.search('[0].url', remote_data) +        if local_data.get('version') != remote_version: +            return {"error": False, +                    "update_available": True, +                    "local_version": local_version, +                    "remote_version": remote_version, +                    "url": image_url} +        return {"update_available": False, +                "local_version": local_version, +                "remote_version": remote_version} + + +def _formatted_compare_version(data): +    local_version = data.get('local_version') +    remote_version = data.get('remote_version') +    url = data.get('url') +    if {'update_available','local_version', 'remote_version', 'url'} <= set(data): +        return f'Current version: {local_version}\n\nUpdate available: {remote_version}\nUpdate URL: {url}' +    elif local_version == remote_version and remote_version is not None: +        return f'No available updates for your system \n' \ +               f'current version: {local_version}\nremote version: {remote_version}' +    else: +        return 'Update not found' + + +def _verify(): +    if not config.exists(base): +        return False +    return True + + +def show_update(raw: bool): +    if not _verify(): +        raise vyos.opmode.UnconfiguredSubsystem("system update-check not configured") +    data = _compare_version_raw() +    if raw: +        return data +    else: +        return _formatted_compare_version(data) + + +if __name__ == '__main__': +    try: +        res = vyos.opmode.run(sys.modules[__name__]) +        if res: +            print(res) +    except (ValueError, vyos.opmode.Error) as e: +        print(e) +        sys.exit(1) diff --git a/src/system/vyos-system-update-check.py b/src/system/vyos-system-update-check.py new file mode 100755 index 000000000..c9597721b --- /dev/null +++ b/src/system/vyos-system-update-check.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +import argparse +import json +import jmespath + +from pathlib import Path +from sys import exit +from time import sleep + +from vyos.util import call + +import vyos.version + +motd_file = Path('/run/motd.d/10-vyos-update') + + +if __name__ == '__main__': +    # Parse command arguments and get config +    parser = argparse.ArgumentParser() +    parser.add_argument('-c', +                        '--config', +                        action='store', +                        help='Path to system-update-check configuration', +                        required=True, +                        type=Path) + +    args = parser.parse_args() +    try: +        config_path = Path(args.config) +        config = json.loads(config_path.read_text()) +    except Exception as err: +        print( +            f'Configuration file "{config_path}" does not exist or malformed: {err}' +        ) +        exit(1) + +    url_json = config.get('url') +    local_data = vyos.version.get_full_version_data() +    local_version = local_data.get('version') + +    while True: +        remote_data = vyos.version.get_remote_version(url_json) +        if remote_data: +            url = jmespath.search('[0].url', remote_data) +            remote_version = jmespath.search('[0].version', remote_data) +            if local_version != remote_version and remote_version: +                call(f'wall -n "Update available: {remote_version} \nUpdate URL: {url}"') +                # MOTD used in /run/motd.d/10-update +                motd_file.parent.mkdir(exist_ok=True) +                motd_file.write_text(f'---\n' +                                     f'Current version: {local_version}\n' +                                     f'Update available: \033[1;34m{remote_version}\033[0m\n' +                                     f'---\n') +        # Check every 12 hours +        sleep(43200) diff --git a/src/systemd/vyos-system-update.service b/src/systemd/vyos-system-update.service new file mode 100644 index 000000000..032e5a14c --- /dev/null +++ b/src/systemd/vyos-system-update.service @@ -0,0 +1,11 @@ +[Unit] +Description=VyOS system udpate-check service +After=network.target vyos-router.service + +[Service] +Type=simple +Restart=always +ExecStart=/usr/bin/python3 /usr/libexec/vyos/system/vyos-system-update-check.py --config /run/vyos-system-update.conf + +[Install] +WantedBy=multi-user.target diff --git a/src/validators/range b/src/validators/range deleted file mode 100755 index d4c25f3c4..000000000 --- a/src/validators/range +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program.  If not, see <http://www.gnu.org/licenses/>. - -import re -import sys -import argparse - -class MalformedRange(Exception): -    pass - -def validate_range(value, min=None, max=None): -    try: -        lower, upper = re.match(r'^(\d+)-(\d+)$', value).groups() - -        lower, upper = int(lower), int(upper) - -        if int(lower) > int(upper): -            raise MalformedRange("the lower bound exceeds the upper bound".format(value)) - -        if min is not None: -            if lower < min: -                raise MalformedRange("the lower bound must not be less than {}".format(min)) - -        if max is not None: -            if upper > max: -                raise MalformedRange("the upper bound must not be greater than {}".format(max)) - -    except (AttributeError, ValueError): -        raise MalformedRange("range syntax error") - -parser = argparse.ArgumentParser(description='Range validator.') -parser.add_argument('--min', type=int, action='store') -parser.add_argument('--max', type=int, action='store') -parser.add_argument('value', action='store') - -if __name__ == '__main__': -    args = parser.parse_args() - -    try: -        validate_range(args.value, min=args.min, max=args.max) -    except MalformedRange as e: -        print("Incorrect range '{}': {}".format(args.value, e)) -        sys.exit(1) | 
