diff options
226 files changed, 5131 insertions, 3775 deletions
diff --git a/.github/reviewers.yml b/.github/reviewers.yml index c99647bb1..8463681fc 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -1,3 +1,8 @@ --- "**/*": - - vyos/maintainers + - dmbaturin + - UnicronNL + - zdc + - jestabro + - sever-sever + - c-po diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index db13eeb5a..9500d3aa7 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -8,7 +8,10 @@ "neighbor.py", "openconnect.py", "route.py", +"system.py", "ipsec.py", +"storage.py", +"uptime.py", "version.py", "vrf.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/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2 index cc1a45d6b..442830b6b 100644 --- a/data/templates/accel-ppp/pptp.config.j2 +++ b/data/templates/accel-ppp/pptp.config.j2 @@ -93,6 +93,15 @@ bind={{ radius_source_address }} gw-ip-address={{ gw_ip }} {% endif %} +{% if radius_shaper_attr %} +[shaper] +verbose=1 +attr={{ radius_shaper_attr }} +{% if radius_shaper_vendor %} +vendor={{ radius_shaper_vendor }} +{% endif %} +{% endif %} + [cli] tcp=127.0.0.1:2003 diff --git a/data/templates/conntrackd/conntrackd.conf.j2 b/data/templates/conntrackd/conntrackd.conf.j2 index 66024869d..808a77759 100644 --- a/data/templates/conntrackd/conntrackd.conf.j2 +++ b/data/templates/conntrackd/conntrackd.conf.j2 @@ -9,7 +9,9 @@ Sync { {% if iface_config.peer is vyos_defined %} UDP { {% if listen_address is vyos_defined %} - IPv4_address {{ listen_address }} +{% for address in listen_address %} + IPv4_address {{ address }} +{% endfor %} {% endif %} IPv4_Destination_Address {{ iface_config.peer }} Port {{ iface_config.port if iface_config.port is vyos_defined else '3780' }} diff --git a/data/templates/conserver/conserver.conf.j2 b/data/templates/conserver/conserver.conf.j2 index 1823657d7..ffd29389d 100644 --- a/data/templates/conserver/conserver.conf.j2 +++ b/data/templates/conserver/conserver.conf.j2 @@ -25,6 +25,9 @@ console {{ key }} { baud {{ value.speed }}; parity {{ value.parity }}; options {{ "!" if value.stop_bits == "1" }}cstopb; +{% if value.alias is vyos_defined %} + aliases "{{ value.alias }}"; +{% endif %} } {% endfor %} 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-geoip-update.j2 b/data/templates/firewall/nftables-geoip-update.j2 index f9e61a274..832ccc3e9 100644 --- a/data/templates/firewall/nftables-geoip-update.j2 +++ b/data/templates/firewall/nftables-geoip-update.j2 @@ -2,10 +2,10 @@ {% if ipv4_sets is vyos_defined %} {% for setname, ip_list in ipv4_sets.items() %} -flush set ip filter {{ setname }} +flush set ip vyos_filter {{ setname }} {% endfor %} -table ip filter { +table ip vyos_filter { {% for setname, ip_list in ipv4_sets.items() %} set {{ setname }} { type ipv4_addr @@ -18,10 +18,10 @@ table ip filter { {% if ipv6_sets is vyos_defined %} {% for setname, ip_list in ipv6_sets.items() %} -flush set ip6 filter {{ setname }} +flush set ip6 vyos_filter {{ setname }} {% endfor %} -table ip6 filter { +table ip6 vyos_filter { {% for setname, ip_list in ipv6_sets.items() %} set {{ setname }} { type ipv6_addr 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-zone.j2 b/data/templates/firewall/nftables-zone.j2 new file mode 100644 index 000000000..17ef5101d --- /dev/null +++ b/data/templates/firewall/nftables-zone.j2 @@ -0,0 +1,78 @@ + +{% macro zone_chains(zone, state_policy=False, ipv6=False) %} +{% set fw_name = 'ipv6_name' if ipv6 else 'name' %} +{% set suffix = '6' if ipv6 else '' %} + chain VYOS_ZONE_FORWARD { + type filter hook forward priority 1; policy accept; +{% if state_policy %} + jump VYOS_STATE_POLICY{{ suffix }} +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if 'local_zone' not in zone_conf %} + oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} +{% endif %} +{% endfor %} + } + chain VYOS_ZONE_LOCAL { + type filter hook input priority 1; policy accept; +{% if state_policy %} + jump VYOS_STATE_POLICY{{ suffix }} +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE_{{ zone_name }}_IN +{% endif %} +{% endfor %} + } + chain VYOS_ZONE_OUTPUT { + type filter hook output priority 1; policy accept; +{% if state_policy %} + jump VYOS_STATE_POLICY{{ suffix }} +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE_{{ zone_name }}_OUT +{% endif %} +{% endfor %} + } +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.local_zone is vyos_defined %} + chain VZONE_{{ zone_name }}_IN { + iifname lo counter return +{% if zone_conf.from is vyos_defined %} +{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endfor %} +{% endif %} + {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + } + chain VZONE_{{ zone_name }}_OUT { + oifname lo counter return +{% if zone_conf.from_local is vyos_defined %} +{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endfor %} +{% endif %} + {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + } +{% else %} + chain VZONE_{{ zone_name }} { + iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }} +{% if zone_conf.intra_zone_filtering is vyos_defined %} + iifname { {{ zone_conf.interface | join(",") }} } counter return +{% endif %} +{% if zone_conf.from is vyos_defined %} +{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} +{% if zone[from_zone].local_zone is not defined %} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endif %} +{% endfor %} +{% endif %} + {{ zone_conf | nft_default_rule('zone_' + zone_name) }} + } +{% endif %} +{% endfor %} +{% endmacro %} diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 5971e1bbc..a0f0b8c11 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -1,25 +1,48 @@ #!/usr/sbin/nft -f {% import 'firewall/nftables-defines.j2' as group_tmpl %} +{% import 'firewall/nftables-zone.j2' as zone_tmpl %} -{% if cleanup_commands is vyos_defined %} -{% for command in cleanup_commands %} -{{ command }} -{% endfor %} +{% if first_install is not vyos_defined %} +delete table ip vyos_filter {% endif %} - -table ip filter { -{% if first_install is vyos_defined %} +table ip vyos_filter { chain VYOS_FW_FORWARD { type filter hook forward priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.in is vyos_defined and ifconf.in.name is vyos_defined %} + iifname {{ ifname }} counter jump NAME_{{ ifconf.in.name }} +{% endif %} +{% if ifconf.out is vyos_defined and ifconf.out.name is vyos_defined %} + oifname {{ ifname }} counter jump NAME_{{ ifconf.out.name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW } chain VYOS_FW_LOCAL { type filter hook input priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.local is vyos_defined and ifconf.local.name is vyos_defined %} + iifname {{ ifname }} counter jump NAME_{{ ifconf.local.name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW } chain VYOS_FW_OUTPUT { type filter hook output priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} jump VYOS_POST_FW } chain VYOS_POST_FW { @@ -29,7 +52,6 @@ table ip filter { type filter hook prerouting priority -450; policy accept; ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return } -{% endif %} {% if name is vyos_defined %} {% set ns = namespace(sets=[]) %} {% for name_text, conf in name.items() %} @@ -72,6 +94,10 @@ table ip filter { {{ group_tmpl.groups(group, False) }} +{% if zone is vyos_defined %} +{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, False) }} +{% endif %} + {% if state_policy is vyos_defined %} chain VYOS_STATE_POLICY { {% if state_policy.established is vyos_defined %} @@ -88,18 +114,46 @@ table ip filter { {% endif %} } -table ip6 filter { -{% if first_install is vyos_defined %} +{% if first_install is not vyos_defined %} +delete table ip6 vyos_filter +{% endif %} +table ip6 vyos_filter { chain VYOS_FW6_FORWARD { type filter hook forward priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.in is vyos_defined and ifconf.in.ipv6_name is vyos_defined %} + iifname {{ ifname }} counter jump NAME6_{{ ifconf.in.ipv6_name }} +{% endif %} +{% if ifconf.out is vyos_defined and ifconf.out.ipv6_name is vyos_defined %} + oifname {{ ifname }} counter jump NAME6_{{ ifconf.out.ipv6_name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW6 } chain VYOS_FW6_LOCAL { type filter hook input priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.local is vyos_defined and ifconf.local.ipv6_name is vyos_defined %} + iifname {{ ifname }} counter jump NAME6_{{ ifconf.local.ipv6_name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW6 } chain VYOS_FW6_OUTPUT { type filter hook output priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} jump VYOS_POST_FW6 } chain VYOS_POST_FW6 { @@ -109,7 +163,6 @@ table ip6 filter { type filter hook prerouting priority -450; policy accept; exthdr frag exists meta mark set 0xffff1 return } -{% endif %} {% if ipv6_name is vyos_defined %} {% set ns = namespace(sets=[]) %} {% for name_text, conf in ipv6_name.items() %} @@ -122,7 +175,7 @@ table ip6 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 %} @@ -144,181 +197,22 @@ table ip6 filter { {{ group_tmpl.groups(group, True) }} +{% if zone is vyos_defined %} +{{ zone_tmpl.zone_chains(zone, state_policy is vyos_defined, True) }} +{% endif %} + {% if state_policy is vyos_defined %} chain VYOS_STATE_POLICY6 { {% if state_policy.established is vyos_defined %} - {{ state_policy.established | nft_state_policy('established', ipv6=True) }} + {{ state_policy.established | nft_state_policy('established') }} {% endif %} {% if state_policy.invalid is vyos_defined %} - {{ state_policy.invalid | nft_state_policy('invalid', ipv6=True) }} + {{ state_policy.invalid | nft_state_policy('invalid') }} {% endif %} {% if state_policy.related is vyos_defined %} - {{ state_policy.related | nft_state_policy('related', ipv6=True) }} + {{ state_policy.related | nft_state_policy('related') }} {% endif %} return } {% endif %} } - -{% if first_install is vyos_defined %} -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 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 - } -} - -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 { - return - } -} - -table inet mangle { - chain FORWARD { - type filter hook forward priority -150; policy accept; - } -} - -table raw { - chain VYOS_TCP_MSS { - type filter hook forward priority -300; policy accept; - } - - chain PREROUTING { - type filter hook prerouting priority -200; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_PREROUTING_HOOK - counter jump FW_CONNTRACK - notrack - } - - chain OUTPUT { - type filter hook output priority -200; policy accept; - counter jump VYOS_CT_IGNORE - counter jump VYOS_CT_TIMEOUT - counter jump VYOS_CT_OUTPUT_HOOK - counter jump FW_CONNTRACK - notrack - } - - ct helper rpc_tcp { - type "rpc" protocol tcp; - } - - ct helper rpc_udp { - type "rpc" protocol udp; - } - - ct helper tns_tcp { - type "tns" protocol tcp; - } - - chain VYOS_CT_HELPER { - ct helper set "rpc_tcp" tcp dport {111} return - ct helper set "rpc_udp" udp dport {111} return - ct helper set "tns_tcp" tcp dport {1521,1525,1536} return - return - } - - chain VYOS_CT_IGNORE { - return - } - - chain VYOS_CT_TIMEOUT { - return - } - - chain VYOS_CT_PREROUTING_HOOK { - return - } - - chain VYOS_CT_OUTPUT_HOOK { - return - } - - chain FW_CONNTRACK { - accept - } -} - -table ip6 raw { - chain VYOS_TCP_MSS { - type filter hook forward priority -300; policy accept; - } - - chain PREROUTING { - type filter hook prerouting priority -300; policy accept; - counter jump VYOS_CT_PREROUTING_HOOK - counter jump FW_CONNTRACK - notrack - } - - chain OUTPUT { - type filter hook output priority -300; policy accept; - counter jump VYOS_CT_OUTPUT_HOOK - counter jump FW_CONNTRACK - notrack - } - - chain VYOS_CT_PREROUTING_HOOK { - return - } - - chain VYOS_CT_OUTPUT_HOOK { - return - } - - chain FW_CONNTRACK { - accept - } -} -{% endif %} diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 808e9dbe7..e8d135c78 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -378,14 +378,19 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endif %} {% endif %} {% if afi_config.route_target.both is vyos_defined %} - route-target both {{ afi_config.route_target.both }} -{% else %} -{% if afi_config.route_target.export is vyos_defined %} - route-target export {{ afi_config.route_target.export }} -{% endif %} -{% if afi_config.route_target.import is vyos_defined %} - route-target import {{ afi_config.route_target.import }} -{% endif %} +{% for route_target in afi_config.route_target.both %} + route-target both {{ route_target }} +{% endfor %} +{% endif %} +{% if afi_config.route_target.export is vyos_defined %} +{% for route_target in afi_config.route_target.export %} + route-target export {{ route_target }} +{% endfor %} +{% endif %} +{% if afi_config.route_target.import is vyos_defined %} +{% for route_target in afi_config.route_target.import %} + route-target import {{ route_target }} +{% endfor %} {% endif %} {% if afi_config.route_map.vpn.export is vyos_defined %} route-map vpn export {{ afi_config.route_map.vpn.export }} @@ -461,6 +466,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 8e95348bc..194dbcb07 100644 --- a/data/templates/frr/isisd.frr.j2 +++ b/data/templates/frr/isisd.frr.j2 @@ -107,9 +107,6 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} mpls-te inter-as{{ level }} {% endif %} {% if segment_routing is vyos_defined %} -{% if segment_routing.enable is vyos_defined %} - segment-routing on -{% endif %} {% if segment_routing.maximum_label_depth is vyos_defined %} segment-routing node-msd {{ segment_routing.maximum_label_depth }} {% endif %} @@ -124,28 +121,27 @@ router isis VyOS {{ 'vrf ' + vrf if vrf is vyos_defined }} {% for prefix, prefix_config in segment_routing.prefix.items() %} {% if prefix_config.absolute is vyos_defined %} {% if prefix_config.absolute.value is vyos_defined %} - segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }} + segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} {% if prefix_config.absolute.explicit_null is vyos_defined %} - segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }} explicit-null -{% endif %} -{% if prefix_config.absolute.no_php_flag is vyos_defined %} - segment-routing prefix {{ prefixes }} absolute {{ prefix_config.absolute.value }} no-php-flag + segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} explicit-null +{% elif prefix_config.absolute.no_php_flag is vyos_defined %} + segment-routing prefix {{ prefix }} absolute {{ prefix_config.absolute.value }} no-php-flag {% endif %} {% endif %} -{% if prefix_config.index is vyos_defined %} -{% if prefix_config.index.value is vyos_defined %} - segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }} -{% if prefix_config.index.explicit_null is vyos_defined %} - segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }} explicit-null -{% endif %} -{% if prefix_config.index.no_php_flag is vyos_defined %} - segment-routing prefix {{ prefixes }} index {{ prefix_config.index.value }} no-php-flag -{% endif %} +{% endif %} +{% if prefix_config.index is vyos_defined %} +{% if prefix_config.index.value 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 +{% elif prefix_config.index.no_php_flag is vyos_defined %} + segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} no-php-flag {% endif %} {% endif %} {% endif %} {% endfor %} {% endif %} + segment-routing on {% endif %} {% if spf_delay_ietf.init_delay is vyos_defined %} spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} short-delay {{ spf_delay_ietf.short_delay }} long-delay {{ spf_delay_ietf.long_delay }} holddown {{ spf_delay_ietf.holddown }} time-to-learn {{ spf_delay_ietf.time_to_learn }} diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2 index 427fc8be7..9cd9b03dc 100644 --- a/data/templates/frr/ospfd.frr.j2 +++ b/data/templates/frr/ospfd.frr.j2 @@ -181,6 +181,33 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if refresh.timers is vyos_defined %} refresh timer {{ refresh.timers }} {% endif %} +{% if segment_routing is vyos_defined %} +{% if segment_routing.maximum_label_depth is vyos_defined %} + segment-routing node-msd {{ segment_routing.maximum_label_depth }} +{% endif %} +{% if segment_routing.global_block is vyos_defined %} +{% if segment_routing.local_block is vyos_defined %} + segment-routing global-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.global_block.high_label_value }} local-block {{ segment_routing.local_block.low_label_value }} {{ segment_routing.local_block.high_label_value }} +{% else %} + segment-routing global-block {{ segment_routing.global_block.low_label_value }} {{ segment_routing.global_block.high_label_value }} +{% endif %} +{% endif %} +{% if segment_routing.prefix is vyos_defined %} +{% for prefix, prefix_config in segment_routing.prefix.items() %} +{% if prefix_config.index is vyos_defined %} +{% if prefix_config.index.value 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 +{% elif prefix_config.index.no_php_flag is vyos_defined %} + segment-routing prefix {{ prefix }} index {{ prefix_config.index.value }} no-php-flag +{% endif %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} + segment-routing on +{% endif %} {% if timers.throttle.spf.delay is vyos_defined and timers.throttle.spf.initial_holdtime is vyos_defined and timers.throttle.spf.max_holdtime is vyos_defined %} {# Timer values have default values #} timers throttle spf {{ timers.throttle.spf.delay }} {{ timers.throttle.spf.initial_holdtime }} {{ timers.throttle.spf.max_holdtime }} diff --git a/data/templates/frr/policy.frr.j2 b/data/templates/frr/policy.frr.j2 index 33df17770..5ad4bd28c 100644 --- a/data/templates/frr/policy.frr.j2 +++ b/data/templates/frr/policy.frr.j2 @@ -274,11 +274,17 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.atomic_aggregate is vyos_defined %} set atomic-aggregate {% endif %} -{% if rule_config.set.comm_list.comm_list is vyos_defined %} - set comm-list {{ rule_config.set.comm_list.comm_list }} {{ 'delete' if rule_config.set.comm_list.delete is vyos_defined }} +{% if rule_config.set.community.delete is vyos_defined %} + set comm-list {{ rule_config.set.community.delete }} delete {% endif %} -{% if rule_config.set.community is vyos_defined %} - set community {{ rule_config.set.community }} +{% if rule_config.set.community.replace is vyos_defined %} + set community {{ rule_config.set.community.replace | join(' ') | replace("local-as" , "local-AS") }} +{% endif %} +{% if rule_config.set.community.add is vyos_defined %} + set community {{ rule_config.set.community.add | join(' ') | replace("local-as" , "local-AS") }} additive +{% endif %} +{% if rule_config.set.community.none is vyos_defined %} + set community none {% endif %} {% if rule_config.set.distance is vyos_defined %} set distance {{ rule_config.set.distance }} @@ -290,13 +296,16 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} set evpn gateway-ip ipv6 {{ rule_config.set.evpn.gateway.ipv6 }} {% endif %} {% if rule_config.set.extcommunity.bandwidth is vyos_defined %} - set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }} + set extcommunity bandwidth {{ rule_config.set.extcommunity.bandwidth }} {{ 'non-transitive' if rule_config.set.extcommunity.bandwidth_non_transitive is vyos_defined }} {% endif %} {% if rule_config.set.extcommunity.rt is vyos_defined %} - set extcommunity rt {{ rule_config.set.extcommunity.rt }} + set extcommunity rt {{ rule_config.set.extcommunity.rt | join(' ') }} {% endif %} {% if rule_config.set.extcommunity.soo is vyos_defined %} - set extcommunity soo {{ rule_config.set.extcommunity.soo }} + set extcommunity soo {{ rule_config.set.extcommunity.soo | join(' ') }} +{% endif %} +{% if rule_config.set.extcommunity.none is vyos_defined %} + set extcommunity none {% endif %} {% if rule_config.set.ip_next_hop is vyos_defined %} set ip next-hop {{ rule_config.set.ip_next_hop }} @@ -313,11 +322,17 @@ route-map {{ route_map }} {{ rule_config.action }} {{ rule }} {% if rule_config.set.ipv6_next_hop.prefer_global is vyos_defined %} set ipv6 next-hop prefer-global {% endif %} -{% if rule_config.set.large_community is vyos_defined %} - set large-community {{ rule_config.set.large_community }} +{% if rule_config.set.large_community.replace is vyos_defined %} + set large-community {{ rule_config.set.large_community.replace | join(' ') }} +{% endif %} +{% if rule_config.set.large_community.add is vyos_defined %} + set large-community {{ rule_config.set.large_community.add | join(' ') }} additive +{% endif %} +{% if rule_config.set.large_community.none is vyos_defined %} + set large-community none {% endif %} -{% if rule_config.set.large_comm_list_delete is vyos_defined %} - set large-comm-list {{ rule_config.set.large_comm_list_delete }} delete +{% if rule_config.set.large_community.delete is vyos_defined %} + set large-comm-list {{ rule_config.set.large_community.delete }} delete {% endif %} {% if rule_config.set.local_preference is vyos_defined %} set local-preference {{ rule_config.set.local_preference }} diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2 index 6684dbc2c..706e1c5ae 100644 --- a/data/templates/high-availability/keepalived.conf.j2 +++ b/data/templates/high-availability/keepalived.conf.j2 @@ -47,10 +47,10 @@ vrrp_instance {{ name }} { {% endif %} {% endif %} {% if group_config.rfc3768_compatibility is vyos_defined and group_config.peer_address is vyos_defined %} - use_vmac {{ group_config.interface }}v{{ group_config.vrid }} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }} vmac_xmit_base {% elif group_config.rfc3768_compatibility is vyos_defined %} - use_vmac {{ group_config.interface }}v{{ group_config.vrid }} + use_vmac {{ group_config.interface }}v{{ group_config.vrid }}v{{ '4' if group_config['address'] | first | is_ipv4 else '6' }} {% endif %} {% if group_config.authentication is vyos_defined %} authentication { diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 index b9f77a257..0340d3c92 100644 --- a/data/templates/ids/fastnetmon.j2 +++ b/data/templates/ids/fastnetmon.j2 @@ -37,18 +37,70 @@ process_incoming_traffic = {{ 'on' if direction is vyos_defined and 'in' in dire process_outgoing_traffic = {{ 'on' if direction is vyos_defined and 'out' in direction else 'off' }} {% if threshold is vyos_defined %} -{% for thr, thr_value in threshold.items() %} -{% if thr is vyos_defined('fps') %} +{% if threshold.general is vyos_defined %} +# General threshold +{% for thr, thr_value in threshold.general.items() %} +{% if thr is vyos_defined('fps') %} ban_for_flows = on threshold_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} +{% elif thr is vyos_defined('mbps') %} ban_for_bandwidth = on threshold_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} +{% elif thr is vyos_defined('pps') %} ban_for_pps = on threshold_pps = {{ thr_value }} -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} +{% endif %} + +{% if threshold.tcp is vyos_defined %} +# TCP threshold +{% for thr, thr_value in threshold.tcp.items() %} +{% if thr is vyos_defined('fps') %} +ban_for_tcp_flows = on +threshold_tcp_flows = {{ thr_value }} +{% elif thr is vyos_defined('mbps') %} +ban_for_tcp_bandwidth = on +threshold_tcp_mbps = {{ thr_value }} +{% elif thr is vyos_defined('pps') %} +ban_for_tcp_pps = on +threshold_tcp_pps = {{ thr_value }} +{% endif %} +{% endfor %} +{% endif %} + +{% if threshold.udp is vyos_defined %} +# UDP threshold +{% for thr, thr_value in threshold.udp.items() %} +{% if thr is vyos_defined('fps') %} +ban_for_udp_flows = on +threshold_udp_flows = {{ thr_value }} +{% elif thr is vyos_defined('mbps') %} +ban_for_udp_bandwidth = on +threshold_udp_mbps = {{ thr_value }} +{% elif thr is vyos_defined('pps') %} +ban_for_udp_pps = on +threshold_udp_pps = {{ thr_value }} +{% endif %} +{% endfor %} +{% endif %} + +{% if threshold.icmp is vyos_defined %} +# ICMP threshold +{% for thr, thr_value in threshold.icmp.items() %} +{% if thr is vyos_defined('fps') %} +ban_for_icmp_flows = on +threshold_icmp_flows = {{ thr_value }} +{% elif thr is vyos_defined('mbps') %} +ban_for_icmp_bandwidth = on +threshold_icmp_mbps = {{ thr_value }} +{% elif thr is vyos_defined('pps') %} +ban_for_icmp_pps = on +threshold_icmp_pps = {{ thr_value }} +{% endif %} +{% endfor %} +{% endif %} + {% endif %} {% if listen_interface is vyos_defined %} diff --git a/data/templates/ipsec/ios_profile.j2 b/data/templates/ipsec/ios_profile.j2 index c8e17729a..eb74924b8 100644 --- a/data/templates/ipsec/ios_profile.j2 +++ b/data/templates/ipsec/ios_profile.j2 @@ -41,7 +41,7 @@ <!-- Remote identity, can be a FQDN, a userFQDN, an IP or (theoretically) a certificate's subject DN. Can't be empty. IMPORTANT: DNs are currently not handled correctly, they are always sent as identities of type FQDN --> <key>RemoteIdentifier</key> - <string>{{ authentication.id if authentication.id is vyos_defined else 'VyOS' }}</string> + <string>{{ authentication.local_id if authentication.local_id is vyos_defined else 'VyOS' }}</string> <!-- Local IKE identity, same restrictions as above. If it is empty the client's IP address will be used --> <key>LocalIdentifier</key> <string></string> 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/login/autologout.j2 b/data/templates/login/autologout.j2 new file mode 100644 index 000000000..dc94eecc3 --- /dev/null +++ b/data/templates/login/autologout.j2 @@ -0,0 +1,5 @@ +{% if timeout is vyos_defined %} +TMOUT={{ timeout }} +readonly TMOUT +export TMOUT +{% endif %} diff --git a/data/templates/login/pam_radius_auth.conf.j2 b/data/templates/login/pam_radius_auth.conf.j2 index 1105b60e5..c61154753 100644 --- a/data/templates/login/pam_radius_auth.conf.j2 +++ b/data/templates/login/pam_radius_auth.conf.j2 @@ -16,7 +16,7 @@ {% if radius.server is vyos_defined %} # server[:port] shared_secret timeout source_ip {# .items() returns a tuple of two elements: key and value. 1 relates to the 2nd element i.e. the value and .priority relates to the key from the internal dict #} -{% for server, options in radius.server.items() | sort(attribute='1.priority') if not options.disabled %} +{% for server, options in radius.server.items() | sort(attribute='1.priority') if not 'disable' in options %} {# RADIUS IPv6 servers must be specified in [] notation #} {% if server | is_ipv4 %} {{ server }}:{{ options.port }} {{ "%-25s" | format(options.key) }} {{ "%-10s" | format(options.timeout) }} {{ source_address.ipv4 if source_address.ipv4 is vyos_defined }} @@ -33,4 +33,3 @@ mapped_priv_user radius_priv_user vrf-name {{ radius.vrf }} {% endif %} {% endif %} - diff --git a/data/templates/nhrp/nftables.conf.j2 b/data/templates/nhrp/nftables.conf.j2 new file mode 100644 index 000000000..a0d1f6d4c --- /dev/null +++ b/data/templates/nhrp/nftables.conf.j2 @@ -0,0 +1,17 @@ +#!/usr/sbin/nft -f + +{% if first_install is not vyos_defined %} +delete table ip vyos_nhrp_filter +{% endif %} +table ip vyos_nhrp_filter { + chain VYOS_NHRP_OUTPUT { + type filter hook output priority 10; policy accept; +{% if tunnel is vyos_defined %} +{% for tun, tunnel_conf in tunnel.items() %} +{% if if_tunnel[tun].source_address is vyos_defined %} + ip protocol gre ip saddr {{ if_tunnel[tun].source_address }} ip daddr 224.0.0.0/4 counter drop comment "VYOS_NHRP_{{ tun }}" +{% endif %} +{% endfor %} +{% endif %} + } +} 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/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2 index 93c6735dd..5bbfdeb88 100644 --- a/data/templates/ssh/sshd_config.j2 +++ b/data/templates/ssh/sshd_config.j2 @@ -96,3 +96,7 @@ DenyGroups {{ access_control.deny.group | join(' ') }} # sshd(8) will send a message through the encrypted channel to request a response from the client ClientAliveInterval {{ client_keepalive_interval }} {% endif %} + +{% if rekey.data is vyos_defined %} +RekeyLimit {{ rekey.data }}M {{ rekey.time + 'M' if rekey.time is vyos_defined }} +{% endif %} diff --git a/data/templates/telegraf/override.conf.j2 b/data/templates/telegraf/override.conf.j2 index d30bb19de..7e3e4aaf5 100644 --- a/data/templates/telegraf/override.conf.j2 +++ b/data/templates/telegraf/override.conf.j2 @@ -12,4 +12,5 @@ EnvironmentFile= Environment=INFLUX_TOKEN={{ influxdb.authentication.token }} CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_BPF CAP_DAC_OVERRIDE AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN - +Restart=always +RestartSec=10 diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 index 6b395692b..36571ce98 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 @@ -110,7 +110,7 @@ server = "unixgram:///run/telegraf/telegraf_syslog.sock" best_effort = true syslog_standard = "RFC3164" -{% if influxdb_configured is vyos_defined %} +{% if influxdb is vyos_defined %} [[inputs.exec]] commands = [ "{{ custom_scripts_dir }}/show_firewall_input_filter.py", diff --git a/data/templates/zone_policy/nftables.j2 b/data/templates/zone_policy/nftables.j2 deleted file mode 100644 index fe941f9f8..000000000 --- a/data/templates/zone_policy/nftables.j2 +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/sbin/nft -f - -{% if cleanup_commands is vyos_defined %} -{% for command in cleanup_commands %} -{{ command }} -{% endfor %} -{% endif %} - -{% if zone is vyos_defined %} -table ip filter { -{% for zone_name, zone_conf in zone.items() if zone_conf.ipv4 %} -{% if zone_conf.local_zone is vyos_defined %} - chain VZONE_{{ zone_name }}_IN { - iifname lo counter return -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is vyos_defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } - chain VZONE_{{ zone_name }}_OUT { - oifname lo counter return -{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.name is vyos_defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} - oifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } -{% else %} - chain VZONE_{{ zone_name }} { - iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=False) }} -{% if zone_conf.intra_zone_filtering is vyos_defined %} - iifname { {{ zone_conf.interface | join(",") }} } counter return -{% endif %} -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.name is vyos_defined %} -{% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME_{{ from_conf.firewall.name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endif %} -{% endfor %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } -{% endif %} -{% endfor %} -} - -table ip6 filter { -{% for zone_name, zone_conf in zone.items() if zone_conf.ipv6 %} -{% if zone_conf.local_zone is vyos_defined %} - chain VZONE6_{{ zone_name }}_IN { - iifname lo counter return -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } - chain VZONE6_{{ zone_name }}_OUT { - oifname lo counter return -{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is vyos_defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - oifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } -{% else %} - chain VZONE6_{{ zone_name }} { - iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }} -{% if zone_conf.intra_zone_filtering is vyos_defined %} - iifname { {{ zone_conf.interface | join(",") }} } counter return -{% endif %} -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} -{% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endif %} -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } -{% endif %} -{% endfor %} -} - -{% for zone_name, zone_conf in zone.items() %} -{% if zone_conf.ipv4 %} -{% if 'local_zone' in zone_conf %} -insert rule ip filter VYOS_FW_LOCAL counter jump VZONE_{{ zone_name }}_IN -insert rule ip filter VYOS_FW_OUTPUT counter jump VZONE_{{ zone_name }}_OUT -{% else %} -insert rule ip filter VYOS_FW_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} -{% endif %} -{% endif %} -{% if zone_conf.ipv6 %} -{% if 'local_zone' in zone_conf %} -insert rule ip6 filter VYOS_FW6_LOCAL counter jump VZONE6_{{ zone_name }}_IN -insert rule ip6 filter VYOS_FW6_OUTPUT counter jump VZONE6_{{ zone_name }}_OUT -{% else %} -insert rule ip6 filter VYOS_FW6_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }} -{% endif %} -{% endif %} -{% endfor %} - -{# Ensure that state-policy rule is first in the chain #} -{% if firewall.state_policy is vyos_defined %} -{% for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] %} -insert rule ip filter {{ chain }} jump VYOS_STATE_POLICY -{% endfor %} -{% for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] %} -insert rule ip6 filter {{ chain }} jump VYOS_STATE_POLICY6 -{% endfor %} -{% endif %} - -{% endif %} diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf new file mode 100644 index 000000000..11a5bc7bf --- /dev/null +++ b/data/vyos-firewall-init.conf @@ -0,0 +1,110 @@ +#!/usr/sbin/nft -f + +# Required by wanloadbalance +table ip nat { + chain VYOS_PRE_SNAT_HOOK { + type nat hook postrouting priority 99; policy accept; + return + } +} + +table inet mangle { + chain FORWARD { + type filter hook forward priority -150; policy accept; + } +} + +table raw { + chain VYOS_TCP_MSS { + type filter hook forward priority -300; policy accept; + } + + chain PREROUTING { + type filter hook prerouting priority -200; policy accept; + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump VYOS_CT_PREROUTING_HOOK + counter jump FW_CONNTRACK + notrack + } + + chain OUTPUT { + type filter hook output priority -200; policy accept; + counter jump VYOS_CT_IGNORE + counter jump VYOS_CT_TIMEOUT + counter jump VYOS_CT_OUTPUT_HOOK + counter jump FW_CONNTRACK + notrack + } + + ct helper rpc_tcp { + type "rpc" protocol tcp; + } + + ct helper rpc_udp { + type "rpc" protocol udp; + } + + ct helper tns_tcp { + type "tns" protocol tcp; + } + + chain VYOS_CT_HELPER { + ct helper set "rpc_tcp" tcp dport {111} return + ct helper set "rpc_udp" udp dport {111} return + ct helper set "tns_tcp" tcp dport {1521,1525,1536} return + return + } + + chain VYOS_CT_IGNORE { + return + } + + chain VYOS_CT_TIMEOUT { + return + } + + chain VYOS_CT_PREROUTING_HOOK { + return + } + + chain VYOS_CT_OUTPUT_HOOK { + return + } + + chain FW_CONNTRACK { + accept + } +} + +table ip6 raw { + chain VYOS_TCP_MSS { + type filter hook forward priority -300; policy accept; + } + + chain PREROUTING { + type filter hook prerouting priority -300; policy accept; + counter jump VYOS_CT_PREROUTING_HOOK + counter jump FW_CONNTRACK + notrack + } + + chain OUTPUT { + type filter hook output priority -300; policy accept; + counter jump VYOS_CT_OUTPUT_HOOK + counter jump FW_CONNTRACK + notrack + } + + chain VYOS_CT_PREROUTING_HOOK { + return + } + + chain VYOS_CT_OUTPUT_HOOK { + return + } + + chain FW_CONNTRACK { + accept + } +} 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/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index 71750b3a1..213a23d9e 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -2,3 +2,4 @@ dpkg-divert --package vyos-1x --add --rename /etc/securetty dpkg-divert --package vyos-1x --add --rename /etc/security/capability.conf dpkg-divert --package vyos-1x --add --rename /lib/systemd/system/lcdproc.service dpkg-divert --package vyos-1x --add --rename /etc/logrotate.d/conntrackd +dpkg-divert --package vyos-1x --add --rename /usr/share/pam-configs/radius diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index c2d652278..673461036 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -314,6 +314,40 @@ </tagNode> </children> </node> + <tagNode name="interface"> + <properties> + <help>Interface name to apply firewall configuration</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + <children> + <node name="in"> + <properties> + <help>Forwarded packets on inbound interface</help> + </properties> + <children> + #include <include/firewall/name.xml.i> + </children> + </node> + <node name="out"> + <properties> + <help>Forwarded packets on outbound interface</help> + </properties> + <children> + #include <include/firewall/name.xml.i> + </children> + </node> + <node name="local"> + <properties> + <help>Packets destined for this router</help> + </properties> + <children> + #include <include/firewall/name.xml.i> + </children> + </node> + </children> + </tagNode> <leafNode name="ip-src-route"> <properties> <help>Policy for handling IPv4 packets with source route option</help> @@ -345,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> @@ -383,17 +425,9 @@ </children> </node> #include <include/firewall/common-rule.xml.i> + #include <include/firewall/dscp.xml.i> #include <include/firewall/packet-length.xml.i> - <node name="hop-limit"> - <properties> - <help>Hop Limit</help> - </properties> - <children> - #include <include/firewall/eq.xml.i> - #include <include/firewall/gt.xml.i> - #include <include/firewall/lt.xml.i> - </children> - </node> + #include <include/firewall/hop-limit.xml.i> <node name="icmpv6"> <properties> <help>ICMPv6 type and code information</help> @@ -426,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> @@ -501,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> @@ -539,6 +589,7 @@ </children> </node> #include <include/firewall/common-rule.xml.i> + #include <include/firewall/dscp.xml.i> #include <include/firewall/packet-length.xml.i> <node name="icmp"> <properties> @@ -572,16 +623,15 @@ #include <include/firewall/icmp-type-name.xml.i> </children> </node> - <node name="ttl"> + <leafNode name="jump-target"> <properties> - <help>Time to live limit</help> + <help>Set jump target. Action jump must be defined to use this setting</help> + <completionHelp> + <path>firewall name</path> + </completionHelp> </properties> - <children> - #include <include/firewall/eq.xml.i> - #include <include/firewall/gt.xml.i> - #include <include/firewall/lt.xml.i> - </children> - </node> + </leafNode> + #include <include/firewall/ttl.xml.i> </children> </tagNode> </children> @@ -661,6 +711,7 @@ </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> @@ -670,6 +721,7 @@ </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> @@ -679,6 +731,7 @@ </properties> <children> #include <include/firewall/action-accept-drop-reject.xml.i> + #include <include/firewall/log.xml.i> #include <include/firewall/rule-log-level.xml.i> </children> </node> @@ -724,6 +777,143 @@ </properties> <defaultValue>disable</defaultValue> </leafNode> + <tagNode name="zone"> + <properties> + <help>Zone-policy</help> + <valueHelp> + <format>txt</format> + <description>Zone name</description> + </valueHelp> + <constraint> + <regex>[a-zA-Z0-9][\w\-\.]*</regex> + </constraint> + </properties> + <children> + #include <include/generic-description.xml.i> + #include <include/firewall/enable-default-log.xml.i> + <leafNode name="default-action"> + <properties> + <help>Default-action for traffic coming into this zone</help> + <completionHelp> + <list>drop reject</list> + </completionHelp> + <valueHelp> + <format>drop</format> + <description>Drop silently</description> + </valueHelp> + <valueHelp> + <format>reject</format> + <description>Drop and notify source</description> + </valueHelp> + <constraint> + <regex>(drop|reject)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> + </leafNode> + <tagNode name="from"> + <properties> + <help>Zone from which to filter traffic</help> + <completionHelp> + <path>zone-policy zone</path> + </completionHelp> + </properties> + <children> + <node name="firewall"> + <properties> + <help>Firewall options</help> + </properties> + <children> + <leafNode name="ipv6-name"> + <properties> + <help>IPv6 firewall ruleset</help> + <completionHelp> + <path>firewall ipv6-name</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>IPv4 firewall ruleset</help> + <completionHelp> + <path>firewall name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + <leafNode name="interface"> + <properties> + <help>Interface associated with zone</help> + <valueHelp> + <format>txt</format> + <description>Interface associated with zone</description> + </valueHelp> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + <multi/> + </properties> + </leafNode> + <node name="intra-zone-filtering"> + <properties> + <help>Intra-zone filtering</help> + </properties> + <children> + <leafNode name="action"> + <properties> + <help>Action for intra-zone traffic</help> + <completionHelp> + <list>accept drop</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept traffic</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop silently</description> + </valueHelp> + <constraint> + <regex>(accept|drop)</regex> + </constraint> + </properties> + </leafNode> + <node name="firewall"> + <properties> + <help>Use the specified firewall chain</help> + </properties> + <children> + <leafNode name="ipv6-name"> + <properties> + <help>IPv6 firewall ruleset</help> + <completionHelp> + <path>firewall ipv6-name</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>IPv4 firewall ruleset</help> + <completionHelp> + <path>firewall name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <leafNode name="local-zone"> + <properties> + <help>Zone to be local-zone</help> + <valueless/> + </properties> + </leafNode> + </children> + </tagNode> </children> </node> </interfaceDefinition> 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/radius-additions-rate-limit.xml.i b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i index f44920c3f..b8dbe73b2 100644 --- a/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i +++ b/interface-definitions/include/accel-ppp/radius-additions-rate-limit.xml.i @@ -6,18 +6,24 @@ <children> <leafNode name="attribute"> <properties> - <help>Specifies which radius attribute contains rate information. (default is Filter-Id)</help> + <help>RADIUS attribute that contains rate information</help> </properties> <defaultValue>Filter-Id</defaultValue> </leafNode> <leafNode name="vendor"> <properties> - <help>Specifies the vendor dictionary. (dictionary needs to be in /usr/share/accel-ppp/radius)</help> + <help>Vendor dictionary</help> + <completionHelp> + <list>alcatel cisco microsoft mikrotik</list> + </completionHelp> + <constraint> + <validator name="accel-radius-dictionary" /> + </constraint> </properties> </leafNode> <leafNode name="enable"> <properties> - <help>Enables Bandwidth shaping via RADIUS</help> + <help>Enable bandwidth shaping via RADIUS</help> <valueless /> </properties> </leafNode> 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/afi-l2vpn-common.xml.i b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i index d586635c8..fef3daf3b 100644 --- a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i +++ b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i @@ -27,6 +27,7 @@ <constraint> <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="import"> @@ -39,6 +40,7 @@ <constraint> <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> + <multi/> </properties> </leafNode> <leafNode name="export"> @@ -51,6 +53,7 @@ <constraint> <validator name="bgp-rd-rt" argument="--route-target"/> </constraint> + <multi/> </properties> </leafNode> </children> 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/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 079864122..a4f66f5cb 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -26,6 +26,14 @@ </leafNode> </children> </node> +<leafNode name="inbound-interface"> + <properties> + <help>Match inbound-interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> +</leafNode> <node name="ipsec"> <properties> <help>Inbound IPsec packets</help> @@ -122,6 +130,14 @@ </leafNode> </children> </node> +<leafNode name="outbound-interface"> + <properties> + <help>Match outbound-interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> +</leafNode> <leafNode name="protocol"> <properties> <help>Protocol to match (protocol name, number, or "all")</help> 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 new file mode 100644 index 000000000..dd4da4894 --- /dev/null +++ b/interface-definitions/include/firewall/dscp.xml.i @@ -0,0 +1,36 @@ +<!-- include start from firewall/dscp.xml.i --> +<leafNode name="dscp"> + <properties> + <help>DSCP value</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP value to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>DSCP range to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-63"/> + </constraint> + <multi/> + </properties> +</leafNode> +<leafNode name="dscp-exclude"> + <properties> + <help>DSCP value not to match</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP value not to match</description> + </valueHelp> + <valueHelp> + <format><start-end></format> + <description>DSCP range not to match</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 0-63"/> + </constraint> + <multi/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/hop-limit.xml.i b/interface-definitions/include/firewall/hop-limit.xml.i new file mode 100644 index 000000000..d375dc985 --- /dev/null +++ b/interface-definitions/include/firewall/hop-limit.xml.i @@ -0,0 +1,12 @@ +<!-- include start from firewall/hop-limit.xml.i --> +<node name="hop-limit"> + <properties> + <help>Hop limit</help> + </properties> + <children> + #include <include/firewall/eq.xml.i> + #include <include/firewall/gt.xml.i> + #include <include/firewall/lt.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/name.xml.i b/interface-definitions/include/firewall/name.xml.i new file mode 100644 index 000000000..231b9b144 --- /dev/null +++ b/interface-definitions/include/firewall/name.xml.i @@ -0,0 +1,18 @@ +<!-- include start from firewall/name.xml.i --> +<leafNode name="name"> + <properties> + <help>Local IPv4 firewall ruleset name for interface</help> + <completionHelp> + <path>firewall name</path> + </completionHelp> + </properties> +</leafNode> +<leafNode name="ipv6-name"> + <properties> + <help>Local IPv6 firewall ruleset name for interface</help> + <completionHelp> + <path>firewall ipv6-name</path> + </completionHelp> + </properties> +</leafNode> +<!-- include end from firewall/name.xml.i -->
\ No newline at end of file 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/firewall/ttl.xml.i b/interface-definitions/include/firewall/ttl.xml.i new file mode 100644 index 000000000..9c782a9a5 --- /dev/null +++ b/interface-definitions/include/firewall/ttl.xml.i @@ -0,0 +1,12 @@ +<!-- include start from firewall/ttl.xml.i --> +<node name="ttl"> + <properties> + <help>Time to live limit</help> + </properties> + <children> + #include <include/firewall/eq.xml.i> + #include <include/firewall/gt.xml.i> + #include <include/firewall/lt.xml.i> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/ids/threshold.xml.i b/interface-definitions/include/ids/threshold.xml.i new file mode 100644 index 000000000..e21e3a005 --- /dev/null +++ b/interface-definitions/include/ids/threshold.xml.i @@ -0,0 +1,38 @@ +<!-- include start from ids/threshold.xml.i --> +<leafNode name="fps"> + <properties> + <help>Flows per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Flows per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<leafNode name="mbps"> + <properties> + <help>Megabits per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Megabits per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<leafNode name="pps"> + <properties> + <help>Packets per second</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Packets per second</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> 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/interface/interface-firewall-vif-c.xml.i b/interface-definitions/include/interface/interface-firewall-vif-c.xml.i deleted file mode 100644 index 1bc235fcb..000000000 --- a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i +++ /dev/null @@ -1,79 +0,0 @@ -<!-- include start from interface/interface-firewall-vif-c.xml.i --> -<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../../@).$VAR(../../@).$VAR(../@)"> - <properties> - <priority>615</priority> - <help>Firewall options</help> - </properties> - <children> - <node name="in"> - <properties> - <help>forwarded packets on inbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Inbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Inbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="out"> - <properties> - <help>forwarded packets on outbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Outbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Outbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="local"> - <properties> - <help>packets destined for this router</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Local IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Local IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/interface/interface-firewall-vif.xml.i b/interface-definitions/include/interface/interface-firewall-vif.xml.i deleted file mode 100644 index a37ac5c4a..000000000 --- a/interface-definitions/include/interface/interface-firewall-vif.xml.i +++ /dev/null @@ -1,79 +0,0 @@ -<!-- include start from interface/interface-firewall-vif.xml.i --> -<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../../@).$VAR(../@)"> - <properties> - <priority>615</priority> - <help>Firewall options</help> - </properties> - <children> - <node name="in"> - <properties> - <help>forwarded packets on inbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Inbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Inbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="out"> - <properties> - <help>forwarded packets on outbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Outbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Outbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="local"> - <properties> - <help>packets destined for this router</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Local IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Local IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/interface/interface-firewall.xml.i b/interface-definitions/include/interface/interface-firewall.xml.i deleted file mode 100644 index b3f20c3bf..000000000 --- a/interface-definitions/include/interface/interface-firewall.xml.i +++ /dev/null @@ -1,79 +0,0 @@ -<!-- include start from interface/interface-firewall.xml.i --> -<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall-interface.py $VAR(../@)"> - <properties> - <priority>615</priority> - <help>Firewall options</help> - </properties> - <children> - <node name="in"> - <properties> - <help>forwarded packets on inbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Inbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Inbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="out"> - <properties> - <help>forwarded packets on outbound interface</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Outbound IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Outbound IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - <node name="local"> - <properties> - <help>packets destined for this router</help> - </properties> - <children> - <leafNode name="name"> - <properties> - <help>Local IPv4 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="ipv6-name"> - <properties> - <help>Local IPv6 firewall ruleset name for interface</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - </children> -</node> -<!-- include end --> diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index c1af9f9e3..916349ade 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -18,7 +18,6 @@ #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall-vif.xml.i> #include <include/interface/interface-policy-vif.xml.i> <leafNode name="protocol"> <properties> @@ -68,7 +67,6 @@ #include <include/interface/mtu-68-16000.xml.i> #include <include/interface/redirect.xml.i> #include <include/interface/vrf.xml.i> - #include <include/interface/interface-firewall-vif-c.xml.i> #include <include/interface/interface-policy-vif-c.xml.i> </children> </tagNode> diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 57ef8d64c..73a8c98ff 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -18,7 +18,6 @@ #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/disable-link-detect.xml.i> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall-vif.xml.i> #include <include/interface/interface-policy-vif.xml.i> <leafNode name="egress-qos"> <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/isis/high-low-label-value.xml.i b/interface-definitions/include/isis/high-low-label-value.xml.i index adc28417d..f30b5af3a 100644 --- a/interface-definitions/include/isis/high-low-label-value.xml.i +++ b/interface-definitions/include/isis/high-low-label-value.xml.i @@ -4,7 +4,7 @@ <help>MPLS label lower bound</help> <valueHelp> <format>u32:16-1048575</format> - <description>Label value</description> + <description>Label value (recommended minimum value: 100)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 16-1048575"/> diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 75a0355d4..57ee19300 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -233,12 +233,6 @@ <help>Segment-Routing (SPRING) settings</help> </properties> <children> - <leafNode name="enable"> - <properties> - <help>Enable segment-routing functionality</help> - <valueless/> - </properties> - </leafNode> <node name="global-block"> <properties> <help>Segment Routing Global Block label range</help> 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/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 791bbc0f8..28e3b473b 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -621,6 +621,86 @@ </constraint> </properties> </leafNode> +<node name="segment-routing"> + <properties> + <help>Segment-Routing (SPRING) settings</help> + </properties> + <children> + <node name="global-block"> + <properties> + <help>Segment Routing Global Block label range</help> + </properties> + <children> + #include <include/isis/high-low-label-value.xml.i> + </children> + </node> + <node name="local-block"> + <properties> + <help>Segment Routing Local Block label range</help> + </properties> + <children> + #include <include/isis/high-low-label-value.xml.i> + </children> + </node> + <leafNode name="maximum-label-depth"> + <properties> + <help>Maximum MPLS labels allowed for this router</help> + <valueHelp> + <format>u32:1-16</format> + <description>MPLS label depth</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-16"/> + </constraint> + </properties> + </leafNode> + <tagNode name="prefix"> + <properties> + <help>Static IPv4 prefix segment/label mapping</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix segment</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + <node name="index"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + </properties> + <children> + <leafNode name="value"> + <properties> + <help>Specify the index value of prefix segment/label ID</help> + <valueHelp> + <format>u32:0-65535</format> + <description>The index segment/label ID value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="explicit-null"> + <properties> + <help>Request upstream neighbor to replace segment/label with explicit null label</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="no-php-flag"> + <properties> + <help>Do not request penultimate hop popping for segment/label</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> + </tagNode> + </children> +</node> <node name="redistribute"> <properties> <help>Redistribute information from another routing protocol</help> diff --git a/interface-definitions/include/policy/community-clear.xml.i b/interface-definitions/include/policy/community-clear.xml.i new file mode 100644 index 000000000..0fd57cdf0 --- /dev/null +++ b/interface-definitions/include/policy/community-clear.xml.i @@ -0,0 +1,8 @@ +<!-- include start from policy/community-clear.xml.i --> +<leafNode name="none"> + <properties> + <help>Completely remove communities attribute from a prefix</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/policy/community-value-list.xml.i b/interface-definitions/include/policy/community-value-list.xml.i new file mode 100644 index 000000000..8c665c5f0 --- /dev/null +++ b/interface-definitions/include/policy/community-value-list.xml.i @@ -0,0 +1,90 @@ +<!-- include start from policy/community-value-list.xml.i --> +<completionHelp> + <list> + local-as + no-advertise + no-export + internet + graceful-shutdown + accept-own + route-filter-translated-v4 + route-filter-v4 + route-filter-translated-v6 + route-filter-v6 + llgr-stale + no-llgr + accept-own-nexthop + blackhole + no-peer + </list> +</completionHelp> +<valueHelp> + <format><AS:VAL></format> + <description>Community number in <0-65535:0-65535> format</description> +</valueHelp> +<valueHelp> + <format>local-as</format> + <description>Well-known communities value NO_EXPORT_SUBCONFED 0xFFFFFF03</description> +</valueHelp> +<valueHelp> + <format>no-advertise</format> + <description>Well-known communities value NO_ADVERTISE 0xFFFFFF02</description> +</valueHelp> +<valueHelp> + <format>no-export</format> + <description>Well-known communities value NO_EXPORT 0xFFFFFF01</description> +</valueHelp> +<valueHelp> + <format>internet</format> + <description>Well-known communities value 0</description> +</valueHelp> +<valueHelp> + <format>graceful-shutdown</format> + <description>Well-known communities value GRACEFUL_SHUTDOWN 0xFFFF0000</description> +</valueHelp> +<valueHelp> + <format>accept-own</format> + <description>Well-known communities value ACCEPT_OWN 0xFFFF0001</description> +</valueHelp> +<valueHelp> + <format>route-filter-translated-v4</format> + <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v4 0xFFFF0002</description> +</valueHelp> +<valueHelp> + <format>route-filter-v4</format> + <description>Well-known communities value ROUTE_FILTER_v4 0xFFFF0003</description> +</valueHelp> +<valueHelp> + <format>route-filter-translated-v6</format> + <description>Well-known communities value ROUTE_FILTER_TRANSLATED_v6 0xFFFF0004</description> +</valueHelp> +<valueHelp> + <format>route-filter-v6</format> + <description>Well-known communities value ROUTE_FILTER_v6 0xFFFF0005</description> +</valueHelp> +<valueHelp> + <format>llgr-stale</format> + <description>Well-known communities value LLGR_STALE 0xFFFF0006</description> +</valueHelp> +<valueHelp> + <format>no-llgr</format> + <description>Well-known communities value NO_LLGR 0xFFFF0007</description> +</valueHelp> +<valueHelp> + <format>accept-own-nexthop</format> + <description>Well-known communities value accept-own-nexthop 0xFFFF0008</description> +</valueHelp> +<valueHelp> + <format>blackhole</format> + <description>Well-known communities value BLACKHOLE 0xFFFF029A</description> +</valueHelp> +<valueHelp> + <format>no-peer</format> + <description>Well-known communities value NOPEER 0xFFFFFF04</description> +</valueHelp> +<multi/> +<constraint> + <regex>local-as|no-advertise|no-export|internet|graceful-shutdown|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|accept-own-nexthop|blackhole|no-peer</regex> + <validator name="bgp-regular-community"/> +</constraint> + <!-- include end --> diff --git a/interface-definitions/include/policy/extended-community-value-list.xml.i b/interface-definitions/include/policy/extended-community-value-list.xml.i new file mode 100644 index 000000000..c79f78c67 --- /dev/null +++ b/interface-definitions/include/policy/extended-community-value-list.xml.i @@ -0,0 +1,15 @@ +<!-- include start from policy/community-value-list.xml.i --> +<valueHelp> + <format>ASN:NN</format> + <description>based on autonomous system number in format <0-65535:0-4294967295></description> +</valueHelp> +<valueHelp> + <format>IP:NN</format> + <description>Based on a router-id IP address in format <IP:0-65535></description> +</valueHelp> +<constraint> + <validator name="bgp-extended-community"/> +</constraint> +<constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> +<multi/> + <!-- include end --> diff --git a/interface-definitions/include/policy/large-community-value-list.xml.i b/interface-definitions/include/policy/large-community-value-list.xml.i new file mode 100644 index 000000000..33b1f13a2 --- /dev/null +++ b/interface-definitions/include/policy/large-community-value-list.xml.i @@ -0,0 +1,10 @@ +<!-- include start from policy/community-value-list.xml.i --> +<valueHelp> + <description>Community in format <0-4294967295:0-4294967295:0-4294967295></description> + <format><GA:LDP1:LDP2></format> +</valueHelp> +<multi/> +<constraint> + <validator name="bgp-large-community"/> +</constraint> + <!-- include end --> diff --git a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i index cfeba1a6c..662206336 100644 --- a/interface-definitions/include/policy/route-common-rule-ipv6.xml.i +++ b/interface-definitions/include/policy/route-common-rule-ipv6.xml.i @@ -198,6 +198,10 @@ <validator name="numeric" argument="--range 1-200"/> <regex>(main)</regex> </constraint> + <completionHelp> + <list>main</list> + <path>protocols static table</path> + </completionHelp> </properties> </leafNode> <leafNode name="tcp-mss"> diff --git a/interface-definitions/include/policy/route-common-rule.xml.i b/interface-definitions/include/policy/route-common-rule.xml.i index 5a17dbc95..35fccca50 100644 --- a/interface-definitions/include/policy/route-common-rule.xml.i +++ b/interface-definitions/include/policy/route-common-rule.xml.i @@ -198,6 +198,10 @@ <validator name="numeric" argument="--range 1-200"/> <regex>(main)</regex> </constraint> + <completionHelp> + <list>main</list> + <path>protocols static table</path> + </completionHelp> </properties> </leafNode> <leafNode name="tcp-mss"> diff --git a/interface-definitions/include/qos/limiter-actions.xml.i b/interface-definitions/include/qos/limiter-actions.xml.i new file mode 100644 index 000000000..a993423aa --- /dev/null +++ b/interface-definitions/include/qos/limiter-actions.xml.i @@ -0,0 +1,66 @@ +<!-- include start from qos/limiter-actions.xml.i --> +<leafNode name="exceed-action"> + <properties> + <help>Default action for packets exceeding the limiter (default: drop)</help> + <completionHelp> + <list>continue drop ok reclassify pipe</list> + </completionHelp> + <valueHelp> + <format>continue</format> + <description>Don't do anything, just continue with the next action in line</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop the packet immediately</description> + </valueHelp> + <valueHelp> + <format>ok</format> + <description>Accept the packet</description> + </valueHelp> + <valueHelp> + <format>reclassify</format> + <description>Treat the packet as non-matching to the filter this action is attached to and continue with the next filter in line (if any)</description> + </valueHelp> + <valueHelp> + <format>pipe</format> + <description>Pass the packet to the next action in line</description> + </valueHelp> + <constraint> + <regex>(continue|drop|ok|reclassify|pipe)</regex> + </constraint> + </properties> + <defaultValue>drop</defaultValue> +</leafNode> +<leafNode name="notexceed-action"> + <properties> + <help>Default action for packets not exceeding the limiter (default: ok)</help> + <completionHelp> + <list>continue drop ok reclassify pipe</list> + </completionHelp> + <valueHelp> + <format>continue</format> + <description>Don't do anything, just continue with the next action in line</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop the packet immediately</description> + </valueHelp> + <valueHelp> + <format>ok</format> + <description>Accept the packet</description> + </valueHelp> + <valueHelp> + <format>reclassify</format> + <description>Treat the packet as non-matching to the filter this action is attached to and continue with the next filter in line (if any)</description> + </valueHelp> + <valueHelp> + <format>pipe</format> + <description>Pass the packet to the next action in line</description> + </valueHelp> + <constraint> + <regex>(continue|drop|ok|reclassify|pipe)</regex> + </constraint> + </properties> + <defaultValue>ok</defaultValue> +</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/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i index 059a89f24..065925319 100644 --- a/interface-definitions/include/version/firewall-version.xml.i +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/firewall-version.xml.i --> -<syntaxVersion component='firewall' version='7'></syntaxVersion> +<syntaxVersion component='firewall' version='8'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/include/version/ids-version.xml.i b/interface-definitions/include/version/ids-version.xml.i new file mode 100644 index 000000000..9133be02b --- /dev/null +++ b/interface-definitions/include/version/ids-version.xml.i @@ -0,0 +1,3 @@ +<!-- include start from include/version/ids-version.xml.i --> +<syntaxVersion component='ids' version='1'></syntaxVersion> +<!-- 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/isis-version.xml.i b/interface-definitions/include/version/isis-version.xml.i index 4a8fef39c..7bf12e81a 100644 --- a/interface-definitions/include/version/isis-version.xml.i +++ b/interface-definitions/include/version/isis-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/isis-version.xml.i --> -<syntaxVersion component='isis' version='1'></syntaxVersion> +<syntaxVersion component='isis' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/include/version/policy-version.xml.i b/interface-definitions/include/version/policy-version.xml.i index 426173a19..89bde20c7 100644 --- a/interface-definitions/include/version/policy-version.xml.i +++ b/interface-definitions/include/version/policy-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/policy-version.xml.i --> -<syntaxVersion component='policy' version='3'></syntaxVersion> +<syntaxVersion component='policy' version='4'></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-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 8b6c6ef62..41e4a68a8 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -56,7 +56,6 @@ #include <include/interface/disable.xml.i> #include <include/interface/vrf.xml.i> #include <include/interface/mirror.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="hash-policy"> <properties> diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 48ee1efbc..1e11cd4c6 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -41,7 +41,6 @@ #include <include/interface/disable.xml.i> #include <include/interface/vrf.xml.i> #include <include/interface/mtu-68-16000.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="forwarding-delay"> <properties> diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 01438de31..fb36741f7 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -19,7 +19,6 @@ #include <include/interface/address-ipv4-ipv6.xml.i> #include <include/interface/description.xml.i> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <node name="ip"> <properties> diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index c821f04b2..77f130e1c 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -31,7 +31,6 @@ </leafNode> #include <include/interface/disable-link-detect.xml.i> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="duplex"> <properties> @@ -94,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/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 6e8a8fee2..b959c787d 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -23,7 +23,6 @@ #include <include/interface/ipv6-options.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/mtu-1450-16000.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <node name="parameters"> <properties> diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in index 2164bfa4e..d01c760f8 100644 --- a/interface-definitions/interfaces-input.xml.in +++ b/interface-definitions/interfaces-input.xml.in @@ -19,7 +19,6 @@ <children> #include <include/interface/description.xml.i> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> #include <include/interface/redirect.xml.i> </children> diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 6a85064cd..bde68dd5a 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -32,7 +32,6 @@ <defaultValue>5000</defaultValue> </leafNode> #include <include/interface/disable.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="encapsulation"> <properties> diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index adb48813f..5c9f4cd76 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -21,7 +21,6 @@ #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> #include <include/interface/mirror.xml.i> <node name="security"> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 6cbd91ff4..3876e31da 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -34,7 +34,6 @@ </children> </node> #include <include/interface/description.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="device-type"> <properties> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 9674cfc0e..84f76a7ee 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -19,7 +19,6 @@ #include <include/pppoe-access-concentrator.xml.i> #include <include/interface/authentication.xml.i> #include <include/interface/dial-on-demand.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> #include <include/interface/no-default-route.xml.i> #include <include/interface/default-route-distance.xml.i> diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 53e6445fa..4eb9bf111 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -28,7 +28,6 @@ #include <include/source-interface-ethernet.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/mirror.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="mode"> <properties> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 98ff878ba..fe49d337a 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -29,7 +29,6 @@ #include <include/source-address-ipv4-ipv6.xml.i> #include <include/interface/tunnel-remote.xml.i> #include <include/source-interface.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="6rd-prefix"> <properties> diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index aa83a04b2..eeaea0dc3 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -25,7 +25,6 @@ #include <include/interface/mirror.xml.i> #include <include/interface/redirect.xml.i> #include <include/interface/vrf.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> </children> </tagNode> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index faa3dd5e0..4902ff36d 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -54,7 +54,6 @@ #include <include/interface/mac.xml.i> #include <include/interface/mtu-1200-16000.xml.i> #include <include/interface/mirror.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <leafNode name="mtu"> <defaultValue>1450</defaultValue> diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 4a1b4ac68..23f50d146 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -21,7 +21,6 @@ #include <include/interface/disable.xml.i> #include <include/port-number.xml.i> #include <include/interface/mtu-68-16000.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> #include <include/interface/mirror.xml.i> <leafNode name="mtu"> diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index daee770a9..9e7fc29bc 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -20,7 +20,6 @@ </properties> <children> #include <include/interface/address-ipv4-ipv6-dhcp.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> <node name="capabilities"> <properties> diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 3071e6091..b0b8367dc 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -39,7 +39,6 @@ #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> #include <include/interface/dial-on-demand.xml.i> - #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> #include <include/interface/redirect.xml.i> #include <include/interface/vrf.xml.i> diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index d969613b1..8619e839e 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -6,6 +6,7 @@ <node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py"> <properties> <help>IPv4 policy route of local traffic</help> + <priority>500</priority> </properties> <children> <tagNode name="rule"> @@ -96,6 +97,7 @@ <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py"> <properties> <help>IPv6 policy route of local traffic</help> + <priority>500</priority> </properties> <children> <tagNode name="rule"> diff --git a/interface-definitions/policy-route.xml.in b/interface-definitions/policy-route.xml.in index c2a9a8d94..f480f3bd5 100644 --- a/interface-definitions/policy-route.xml.in +++ b/interface-definitions/policy-route.xml.in @@ -47,6 +47,9 @@ </children> </node> #include <include/policy/route-common-rule-ipv6.xml.i> + #include <include/firewall/dscp.xml.i> + #include <include/firewall/packet-length.xml.i> + #include <include/firewall/hop-limit.xml.i> </children> </tagNode> </children> @@ -96,6 +99,9 @@ </children> </node> #include <include/policy/route-common-rule.xml.i> + #include <include/firewall/dscp.xml.i> + #include <include/firewall/packet-length.xml.i> + #include <include/firewall/ttl.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in index e794c4b90..6c60276d5 100644 --- a/interface-definitions/policy.xml.in +++ b/interface-definitions/policy.xml.in @@ -1118,67 +1118,120 @@ <valueless/> </properties> </leafNode> - <node name="comm-list"> + <node name="community"> <properties> - <help>BGP communities matching a community-list</help> + <help>BGP community attribute</help> </properties> <children> - <leafNode name="comm-list"> + <leafNode name="add"> + <properties> + <help>Add communities to a prefix</help> + #include <include/policy/community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="replace"> + <properties> + <help>Set communities for a prefix</help> + #include <include/policy/community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> + <leafNode name="delete"> <properties> - <help>BGP communities with a community-list</help> + <help>Remove communities defined in a list from a prefix</help> <completionHelp> <path>policy community-list</path> </completionHelp> <valueHelp> + <description>Community-list</description> <format>txt</format> - <description>BGP communities with a community-list</description> </valueHelp> </properties> </leafNode> + </children> + </node> + <node name="large-community"> + <properties> + <help>BGP large community attribute</help> + </properties> + <children> + <leafNode name="add"> + <properties> + <help>Add large communities to a prefix ;</help> + #include <include/policy/large-community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="replace"> + <properties> + <help>Set large communities for a prefix</help> + #include <include/policy/large-community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> <leafNode name="delete"> <properties> - <help>Delete BGP communities matching the community-list</help> - <valueless/> + <help>Remove communities defined in a list from a prefix</help> + <completionHelp> + <path>policy large-community-list</path> + </completionHelp> + <valueHelp> + <description>Community-list</description> + <format>txt</format> + </valueHelp> </properties> </leafNode> </children> </node> - <leafNode name="community"> + <node name="extcommunity"> <properties> - <help>Border Gateway Protocl (BGP) community attribute</help> - <completionHelp> - <list>local-AS no-advertise no-export internet additive none</list> - </completionHelp> - <valueHelp> - <format><aa:nn></format> - <description>Community number in AA:NN format</description> - </valueHelp> - <valueHelp> - <format>local-AS</format> - <description>Well-known communities value NO_EXPORT_SUBCONFED 0xFFFFFF03</description> - </valueHelp> - <valueHelp> - <format>no-advertise</format> - <description>Well-known communities value NO_ADVERTISE 0xFFFFFF02</description> - </valueHelp> - <valueHelp> - <format>no-export</format> - <description>Well-known communities value NO_EXPORT 0xFFFFFF01</description> - </valueHelp> - <valueHelp> - <format>internet</format> - <description>Well-known communities value 0</description> - </valueHelp> - <valueHelp> - <format>additive</format> - <description>New value is appended to the existing value</description> - </valueHelp> - <valueHelp> - <format>none</format> - <description>No community attribute</description> - </valueHelp> + <help>BGP extended community attribute</help> </properties> - </leafNode> + <children> + <leafNode name="bandwidth"> + <properties> + <help>Bandwidth value in Mbps</help> + <completionHelp> + <list>cumulative num-multipaths</list> + </completionHelp> + <valueHelp> + <format>u32:1-25600</format> + <description>Bandwidth value in Mbps</description> + </valueHelp> + <valueHelp> + <format>cumulative</format> + <description>Cumulative bandwidth of all multipaths (outbound-only)</description> + </valueHelp> + <valueHelp> + <format>num-multipaths</format> + <description>Internally computed bandwidth based on number of multipaths (outbound-only)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-25600"/> + <regex>(cumulative|num-multipaths)</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="bandwidth-non-transitive"> + <properties> + <help>The link bandwidth extended community is encoded as non-transitive</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="rt"> + <properties> + <help>Set route target value</help> + #include <include/policy/extended-community-value-list.xml.i> + </properties> + </leafNode> + <leafNode name="soo"> + <properties> + <help>Set Site of Origin value</help> + #include <include/policy/extended-community-value-list.xml.i> + </properties> + </leafNode> + #include <include/policy/community-clear.xml.i> + </children> + </node> <leafNode name="distance"> <properties> <help>Locally significant administrative distance</help> @@ -1229,71 +1282,6 @@ </node> </children> </node> - <node name="extcommunity"> - <properties> - <help>BGP extended community attribute</help> - </properties> - <children> - <leafNode name="bandwidth"> - <properties> - <help>Bandwidth value in Mbps</help> - <completionHelp> - <list>cumulative num-multipaths</list> - </completionHelp> - <valueHelp> - <format>u32:1-25600</format> - <description>Bandwidth value in Mbps</description> - </valueHelp> - <valueHelp> - <format>cumulative</format> - <description>Cumulative bandwidth of all multipaths (outbound-only)</description> - </valueHelp> - <valueHelp> - <format>num-multipaths</format> - <description>Internally computed bandwidth based on number of multipaths (outbound-only)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-25600"/> - <regex>(cumulative|num-multipaths)</regex> - </constraint> - </properties> - </leafNode> - <leafNode name="rt"> - <properties> - <help>Set route target value</help> - <valueHelp> - <format>ASN:NN</format> - <description>based on autonomous system number</description> - </valueHelp> - <valueHelp> - <format>IP:NN</format> - <description>Based on a router-id IP address</description> - </valueHelp> - <constraint> - <regex>(((\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)|(\d+)):(\d+) ?)+</regex> - </constraint> - <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="soo"> - <properties> - <help>Set Site of Origin value</help> - <valueHelp> - <format>ASN:NN</format> - <description>based on autonomous system number</description> - </valueHelp> - <valueHelp> - <format>IP:NN</format> - <description>Based on a router-id IP address</description> - </valueHelp> - <constraint> - <regex>((?:[0-9]{1,3}\.){3}[0-9]{1,3}|\d+):\d+</regex> - </constraint> - <constraintErrorMessage>Should be in form: ASN:NN or IPADDR:NN where ASN is autonomous system number</constraintErrorMessage> - </properties> - </leafNode> - </children> - </node> <leafNode name="ip-next-hop"> <properties> <help>Nexthop IP address</help> @@ -1368,30 +1356,6 @@ </leafNode> </children> </node> - <leafNode name="large-community"> - <properties> - <help>Set BGP large community value</help> - <valueHelp> - <format>txt</format> - <description>ASN:nn:mm BGP large community</description> - </valueHelp> - <completionHelp> - <path>policy large-community-list</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="large-comm-list-delete"> - <properties> - <help>Delete BGP communities matching the large community-list</help> - <completionHelp> - <path>policy large-community-list</path> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>BGP large community-list</description> - </valueHelp> - </properties> - </leafNode> <leafNode name="local-preference"> <properties> <help>BGP local preference attribute</help> diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in index e8f575a1e..e2dbcbeef 100644 --- a/interface-definitions/qos.xml.in +++ b/interface-definitions/qos.xml.in @@ -188,6 +188,7 @@ #include <include/qos/burst.xml.i> #include <include/generic-description.xml.i> #include <include/qos/match.xml.i> + #include <include/qos/limiter-actions.xml.i> <leafNode name="priority"> <properties> <help>Priority for rule evaluation</help> @@ -211,6 +212,7 @@ <children> #include <include/qos/bandwidth.xml.i> #include <include/qos/burst.xml.i> + #include <include/qos/limiter-actions.xml.i> </children> </node> #include <include/generic-description.xml.i> diff --git a/interface-definitions/service-console-server.xml.in b/interface-definitions/service-console-server.xml.in index e9591ad87..fb71538dd 100644 --- a/interface-definitions/service-console-server.xml.in +++ b/interface-definitions/service-console-server.xml.in @@ -28,6 +28,14 @@ </properties> <children> #include <include/interface/description.xml.i> + <leafNode name="alias"> + <properties> + <help>Human-readable name for this console</help> + <constraint> + <regex>[-_a-zA-Z0-9.]{1,128}</regex> + </constraint> + </properties> + </leafNode> <leafNode name="speed"> <properties> <help>Serial port baud rate</help> diff --git a/interface-definitions/service-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in index 86fc4dffa..a661b845d 100644 --- a/interface-definitions/service-ids-ddos-protection.xml.in +++ b/interface-definitions/service-ids-ddos-protection.xml.in @@ -107,42 +107,38 @@ <help>Attack limits thresholds</help> </properties> <children> - <leafNode name="fps"> + <node name="general"> <properties> - <help>Flows per second</help> - <valueHelp> - <format>u32:0-4294967294</format> - <description>Flows per second</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-4294967294"/> - </constraint> + <help>General threshold</help> </properties> - </leafNode> - <leafNode name="mbps"> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="tcp"> <properties> - <help>Megabits per second</help> - <valueHelp> - <format>u32:0-4294967294</format> - <description>Megabits per second</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-4294967294"/> - </constraint> + <help>TCP threshold</help> </properties> - </leafNode> - <leafNode name="pps"> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="udp"> <properties> - <help>Packets per second</help> - <valueHelp> - <format>u32:0-4294967294</format> - <description>Packets per second</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-4294967294"/> - </constraint> + <help>UDP threshold</help> </properties> - </leafNode> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> + <node name="icmp"> + <properties> + <help>ICMP threshold</help> + </properties> + <children> + #include <include/ids/threshold.xml.i> + </children> + </node> </children> </node> </children> 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/ssh.xml.in b/interface-definitions/ssh.xml.in index 126183162..f3c731fe5 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -206,6 +206,37 @@ </properties> <defaultValue>22</defaultValue> </leafNode> + <node name="rekey"> + <properties> + <help>SSH session rekey limit</help> + </properties> + <children> + <leafNode name="data"> + <properties> + <help>Threshold data in megabytes</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Megabytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="time"> + <properties> + <help>Threshold time in minutes</help> + <valueHelp> + <format>u32:1-65535</format> + <description>Minutes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + </properties> + </leafNode> + </children> + </node> <leafNode name="client-keepalive-interval"> <properties> <help>Enable transmission of keepalives from server to client</help> 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-login.xml.in b/interface-definitions/system-login.xml.in index 79c7c4791..7dd045e6c 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -227,6 +227,19 @@ #include <include/interface/vrf.xml.i> </children> </node> + <leafNode name="timeout"> + <properties> + <help>Session timeout</help> + <valueHelp> + <format>u32:5-604800</format> + <description>Session timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-604800"/> + </constraint> + <constraintErrorMessage>Timeout must be between 5 and 604800 seconds</constraintErrorMessage> + </properties> + </leafNode> </children> </node> </children> diff --git a/interface-definitions/system-option.xml.in b/interface-definitions/system-option.xml.in index 8cd25799b..a9fed81fe 100644 --- a/interface-definitions/system-option.xml.in +++ b/interface-definitions/system-option.xml.in @@ -36,7 +36,7 @@ <properties> <help>System keyboard layout, type ISO2</help> <completionHelp> - <list>us fr de fi no dk dvorak</list> + <list>us fr de es fi jp106 no dk dvorak</list> </completionHelp> <valueHelp> <format>us</format> @@ -51,10 +51,18 @@ <description>Germany</description> </valueHelp> <valueHelp> + <format>es</format> + <description>Spain</description> + </valueHelp> + <valueHelp> <format>fi</format> <description>Finland</description> </valueHelp> <valueHelp> + <format>jp106</format> + <description>Japan</description> + </valueHelp> + <valueHelp> <format>no</format> <description>Norway</description> </valueHelp> @@ -66,6 +74,10 @@ <format>dvorak</format> <description>Dvorak</description> </valueHelp> + <constraint> + <regex>(us|fr|de|es|fi|jp106|no|dk|dvorak)</regex> + </constraint> + <constraintErrorMessage>Invalid keyboard layout</constraintErrorMessage> </properties> <defaultValue>us</defaultValue> </leafNode> 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-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index fd70a76dc..cb5900e0d 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -238,29 +238,7 @@ </leafNode> </children> </node> - <node name="rate-limit"> - <properties> - <help>Upload/Download speed limits</help> - </properties> - <children> - <leafNode name="attribute"> - <properties> - <help>Specifies which radius attribute contains rate information</help> - </properties> - </leafNode> - <leafNode name="vendor"> - <properties> - <help>Specifies the vendor dictionary. (dictionary needs to be in /usr/share/accel-ppp/radius)</help> - </properties> - </leafNode> - <leafNode name="enable"> - <properties> - <help>Enables Bandwidth shaping via RADIUS</help> - <valueless /> - </properties> - </leafNode> - </children> - </node> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> </children> </node> </children> diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 6309863c5..3b3a83bd4 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/interface-definitions/vpn-pptp.xml.in b/interface-definitions/vpn-pptp.xml.in index 28a53acb9..5e52965fd 100644 --- a/interface-definitions/vpn-pptp.xml.in +++ b/interface-definitions/vpn-pptp.xml.in @@ -110,6 +110,7 @@ </node> #include <include/radius-server-ipv4.xml.i> #include <include/accel-ppp/radius-additions.xml.i> + #include <include/accel-ppp/radius-additions-rate-limit.xml.i> </children> </node> </children> diff --git a/interface-definitions/xml-component-version.xml.in b/interface-definitions/xml-component-version.xml.in index cf86f83d6..914e3bc69 100644 --- a/interface-definitions/xml-component-version.xml.in +++ b/interface-definitions/xml-component-version.xml.in @@ -14,6 +14,7 @@ #include <include/version/flow-accounting-version.xml.i> #include <include/version/https-version.xml.i> #include <include/version/interfaces-version.xml.i> + #include <include/version/ids-version.xml.i> #include <include/version/ipoe-server-version.xml.i> #include <include/version/ipsec-version.xml.i> #include <include/version/isis-version.xml.i> diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in deleted file mode 100644 index dc3408c3d..000000000 --- a/interface-definitions/zone-policy.xml.in +++ /dev/null @@ -1,148 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="zone-policy" owner="${vyos_conf_scripts_dir}/zone_policy.py"> - <properties> - <help>Configure zone-policy</help> - <priority>250</priority> - </properties> - <children> - <tagNode name="zone"> - <properties> - <help>Zone name</help> - <valueHelp> - <format>txt</format> - <description>Zone name</description> - </valueHelp> - <constraint> - <regex>[a-zA-Z0-9][\w\-\.]*</regex> - </constraint> - </properties> - <children> - #include <include/generic-description.xml.i> - #include <include/firewall/enable-default-log.xml.i> - <leafNode name="default-action"> - <properties> - <help>Default-action for traffic coming into this zone</help> - <completionHelp> - <list>drop reject</list> - </completionHelp> - <valueHelp> - <format>drop</format> - <description>Drop silently</description> - </valueHelp> - <valueHelp> - <format>reject</format> - <description>Drop and notify source</description> - </valueHelp> - <constraint> - <regex>(drop|reject)</regex> - </constraint> - </properties> - <defaultValue>drop</defaultValue> - </leafNode> - <tagNode name="from"> - <properties> - <help>Zone from which to filter traffic</help> - <completionHelp> - <path>zone-policy zone</path> - </completionHelp> - </properties> - <children> - <node name="firewall"> - <properties> - <help>Firewall options</help> - </properties> - <children> - <leafNode name="ipv6-name"> - <properties> - <help>IPv6 firewall ruleset</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="name"> - <properties> - <help>IPv4 firewall ruleset</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - </children> - </tagNode> - <leafNode name="interface"> - <properties> - <help>Interface associated with zone</help> - <valueHelp> - <format>txt</format> - <description>Interface associated with zone</description> - </valueHelp> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> - </completionHelp> - <multi/> - </properties> - </leafNode> - <node name="intra-zone-filtering"> - <properties> - <help>Intra-zone filtering</help> - </properties> - <children> - <leafNode name="action"> - <properties> - <help>Action for intra-zone traffic</help> - <completionHelp> - <list>accept drop</list> - </completionHelp> - <valueHelp> - <format>accept</format> - <description>Accept traffic</description> - </valueHelp> - <valueHelp> - <format>drop</format> - <description>Drop silently</description> - </valueHelp> - <constraint> - <regex>(accept|drop)</regex> - </constraint> - </properties> - </leafNode> - <node name="firewall"> - <properties> - <help>Use the specified firewall chain</help> - </properties> - <children> - <leafNode name="ipv6-name"> - <properties> - <help>IPv6 firewall ruleset</help> - <completionHelp> - <path>firewall ipv6-name</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="name"> - <properties> - <help>IPv4 firewall ruleset</help> - <completionHelp> - <path>firewall name</path> - </completionHelp> - </properties> - </leafNode> - </children> - </node> - </children> - </node> - <leafNode name="local-zone"> - <properties> - <help>Zone to be local-zone</help> - <valueless/> - </properties> - </leafNode> - </children> - </tagNode> - </children> - </node> -</interfaceDefinition> diff --git a/op-mode-definitions/clear-session.xml.in b/op-mode-definitions/clear-session.xml.in new file mode 100644 index 000000000..bfafe6312 --- /dev/null +++ b/op-mode-definitions/clear-session.xml.in @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="clear"> + <children> + <tagNode name="session"> + <properties> + <help>Terminate TTY or PTS user session</help> + <completionHelp> + <script>who | awk '{print $2}'</script> + </completionHelp> + </properties> + <command>sudo pkill -9 -t $3</command> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in index 8f19eac70..d0c93195c 100644 --- a/op-mode-definitions/connect.xml.in +++ b/op-mode-definitions/connect.xml.in @@ -10,6 +10,7 @@ <help>Connect to device attached to serial console server</help> <completionHelp> <path>service console-server device</path> + <script>${vyos_completion_dir}/list_consoles.sh</script> </completionHelp> </properties> <command>/usr/bin/console "$3"</command> diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i index d2804e3b3..7dbc4fde5 100644 --- a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i @@ -153,7 +153,7 @@ <properties> <help>Show BGP information for specified neighbor</help> <completionHelp> - <script>vtysh -c 'show bgp summary' | awk '{print $1'} | grep -e '^[0-9a-f]'</script> + <script>vtysh -c "$(IFS=$' '; echo "${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-2} summary")" | awk '/^[0-9a-f]/ {print $1}'</script> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 975d20465..01462ad8f 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -237,7 +237,7 @@ </leafNode> <leafNode name="ipsec"> <properties> - <help>Monitor last lines of IPSec</help> + <help>Monitor last lines of IPsec</help> </properties> <command>journalctl --no-hostname --boot --follow --unit strongswan-starter.service</command> </leafNode> diff --git a/op-mode-definitions/show-console-server.xml.in b/op-mode-definitions/show-console-server.xml.in index 253d15498..eae6fd536 100644 --- a/op-mode-definitions/show-console-server.xml.in +++ b/op-mode-definitions/show-console-server.xml.in @@ -8,7 +8,7 @@ <properties> <help>Show log for serial console server</help> </properties> - <command>/usr/bin/journalctl --unit conserver-server.service</command> + <command>journalctl --no-hostname --boot --follow --unit conserver-server.service</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-interfaces-pppoe.xml.in b/op-mode-definitions/show-interfaces-pppoe.xml.in index 767836abf..09608bc04 100644 --- a/op-mode-definitions/show-interfaces-pppoe.xml.in +++ b/op-mode-definitions/show-interfaces-pppoe.xml.in @@ -17,7 +17,7 @@ <properties> <help>Show specified PPPoE interface log</help> </properties> - <command>/usr/bin/journalctl --unit "ppp@$4".service</command> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command> </leafNode> <leafNode name="statistics"> <properties> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index ebd198215..8906d9ef3 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -384,7 +384,7 @@ </leafNode> <leafNode name="ipsec"> <properties> - <help>Show log for IPSec</help> + <help>Show log for IPsec</help> </properties> <command>journalctl --no-hostname --boot --unit strongswan-starter.service</command> </leafNode> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index 60ed28b6f..4a0e6c3b2 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -142,7 +142,7 @@ <properties> <help>Show summary of system processes</help> </properties> - <command>${vyos_op_scripts_dir}/show_uptime.py</command> + <command>${vyos_op_scripts_dir}/uptime.py show</command> </leafNode> <leafNode name="tree"> <properties> @@ -162,13 +162,19 @@ <properties> <help>Show filesystem usage</help> </properties> - <command>df -h -x squashfs</command> + <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> </properties> - <command>${vyos_op_scripts_dir}/show_uptime.py</command> + <command>${vyos_op_scripts_dir}/uptime.py show</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index 8c9e76651..f1af65fcb 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -55,9 +55,9 @@ <children> <node name="vpn"> <properties> - <help>Restart IPSec VPN</help> + <help>Restart the IPsec VPN process</help> </properties> - <command>if pgrep charon >/dev/null ; then sudo ipsec restart ; sleep 3 ; sudo swanctl -q ; else echo "IPSec process not running" ; fi</command> + <command>if pgrep charon >/dev/null ; then sudo ipsec restart ; sleep 3 ; sudo swanctl -q ; else echo "IPsec process not running" ; fi</command> </node> </children> </node> @@ -134,7 +134,7 @@ </node> <node name="ipsec"> <properties> - <help>Show Internet Protocol Security (IPSec) information</help> + <help>Show Internet Protocol Security (IPsec) information</help> </properties> <children> <node name="policy"> @@ -151,19 +151,19 @@ </leafNode> <node name="sa"> <properties> - <help>Show all active IPSec Security Associations (SA)</help> + <help>Show all active IPsec Security Associations (SA)</help> </properties> <children> <!-- <node name="detail"> <properties> - <help>Show Detail on all active IPSec Security Associations (SA)</help> + <help>Show Detail on all active IPsec Security Associations (SA)</help> </properties> <command></command> </node> <tagNode name="stats"> <properties> - <help>Show statistics for all currently active IPSec Security Associations (SA)</help> + <help>Show statistics for all currently active IPsec Security Associations (SA)</help> <valueHelp> <format>txt</format> <description>Show Statistics for SAs associated with a specific peer</description> @@ -182,12 +182,12 @@ --> <node name="verbose"> <properties> - <help>Show Verbose Detail on all active IPSec Security Associations (SA)</help> + <help>Show Verbose Detail on all active IPsec Security Associations (SA)</help> </properties> - <command>if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPSec process not running" ; fi</command> + <command>if pgrep charon >/dev/null ; then sudo /usr/sbin/ipsec statusall ; else echo "IPsec process not running" ; fi</command> </node> </children> - <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPSec process not running" ; fi</command> + <command>if pgrep charon >/dev/null ; then sudo ${vyos_op_scripts_dir}/ipsec.py show_sa ; else echo "IPsec process not running" ; fi</command> </node> <node name="state"> <properties> @@ -197,9 +197,9 @@ </node> <node name="status"> <properties> - <help>Show status of IPSec process</help> + <help>Show status of IPsec process</help> </properties> - <command>if pgrep charon >/dev/null ; then echo -e "IPSec Process Running: $(pgrep charon)\n$(sudo /usr/sbin/ipsec status)" ; else echo "IPSec process not running" ; fi</command> + <command>if pgrep charon >/dev/null ; then echo -e "IPsec Process Running: $(pgrep charon)\n$(sudo /usr/sbin/ipsec status)" ; else echo "IPsec process not running" ; fi</command> </node> </children> </node> 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 0bc5378db..4075e55b0 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -52,9 +52,9 @@ def get_ips_domains_dict(list_domains): return ip_dict -def nft_init_set(group_name, table="filter", family="ip"): +def nft_init_set(group_name, table="vyos_filter", family="ip"): """ - table ip filter { + table ip vyos_filter { set GROUP_NAME type ipv4_addr flags interval @@ -63,9 +63,9 @@ def nft_init_set(group_name, table="filter", family="ip"): return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}') -def nft_add_set_elements(group_name, elements, table="filter", family="ip"): +def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"): """ - table ip filter { + table ip vyos_filter { set GROUP_NAME { type ipv4_addr flags interval @@ -75,18 +75,18 @@ def nft_add_set_elements(group_name, elements, table="filter", family="ip"): elements = ", ".join(elements) return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ') -def nft_flush_set(group_name, table="filter", family="ip"): +def nft_flush_set(group_name, table="vyos_filter", family="ip"): """ Flush elements of nft set """ return call(f'nft flush set {family} {table} {group_name}') -def nft_update_set_elements(group_name, elements, table="filter", family="ip"): +def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"): """ Update elements of nft set """ - flush_set = nft_flush_set(group_name, table="filter", family="ip") - nft_add_set = nft_add_set_elements(group_name, elements, table="filter", family="ip") + flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip") + nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip") return flush_set, nft_add_set # END firewall group domain-group (sets) @@ -248,6 +248,14 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): value = rule_conf['hop_limit'][op] output.append(f'ip6 hoplimit {operator} {value}') + if 'inbound_interface' in rule_conf: + iiface = rule_conf['inbound_interface'] + output.append(f'iifname {iiface}') + + if 'outbound_interface' in rule_conf: + oiface = rule_conf['outbound_interface'] + output.append(f'oifname {oiface}') + if 'ttl' in rule_conf: operators = {'eq': '==', 'gt': '>', 'lt': '<'} for op, operator in operators.items(): @@ -274,6 +282,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): negated_lengths_str = ','.join(rule_conf['packet_length_exclude']) output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}') + if 'dscp' in rule_conf: + dscp_str = ','.join(rule_conf['dscp']) + output.append(f'ip{def_suffix} dscp {{{dscp_str}}}') + + if 'dscp_exclude' in rule_conf: + negated_dscp_str = ','.join(rule_conf['dscp_exclude']) + output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}') if 'ipsec' in rule_conf: if 'match_ipsec' in rule_conf['ipsec']: @@ -319,6 +334,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..519cfc58c 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 @@ -69,13 +70,6 @@ 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', - }, - }} - def __init__(self, ifname, **kargs): super().__init__(ifname, **kargs) self.ethtool = Ethtool(ifname) @@ -246,6 +240,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 +250,23 @@ 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 + queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) + if state: + global_rfs_flow = 32768 + rfs_flow = int(global_rfs_flow/queues) + + 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 +352,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..2a4135f9e 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,16 +557,26 @@ 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) @register_filter('nft_state_policy') -def nft_state_policy(conf, state, ipv6=False): +def nft_state_policy(conf, state): out = [f'ct state {state}'] - if 'log' in conf: - log_level = conf['log'] - out.append(f'log level {log_level}') + if 'log' in conf and 'enable' in conf['log']: + log_state = state[:3].upper() + log_action = (conf['action'] if 'action' in conf else 'accept')[:1].upper() + out.append(f'log prefix "[STATE-POLICY-{log_state}-{log_action}]"') + + if 'log_level' in conf: + log_level = conf['log_level'] + out.append(f'level {log_level}') out.append('counter') @@ -611,6 +621,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/util.py b/python/vyos/util.py index 325b630bc..461df9a6e 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -471,6 +471,12 @@ def process_named_running(name): return p.pid return None +def is_list_equal(first: list, second: list) -> bool: + """ Check if 2 lists are equal and list not empty """ + if len(first) != len(second) or len(first) == 0: + return False + return sorted(first) == sorted(second) + def is_listen_port_bind_service(port: int, service: str) -> bool: """Check if listen port bound to expected program name :param port: Bind port 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 1517180de..821925bcd 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -36,8 +36,6 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'} } -eth0_addr = '172.16.10.1/24' - class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -47,15 +45,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, ['firewall']) - cls.cli_set(cls, ['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) - @classmethod def tearDownClass(cls): - cls.cli_delete(cls, ['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) super(TestFirewall, cls).tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'ethernet', 'eth0', 'firewall']) self.cli_delete(['firewall']) self.cli_commit() @@ -69,7 +63,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['chain NAME_smoketest'] ] - self.verify_nftables(nftables_search, 'ip filter', inverse=True) + self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True) def verify_nftables(self, nftables_search, table, inverse=False, args=''): nftables_output = cmd(f'sudo nft {args} list table {table}') @@ -99,7 +93,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ] # -t prevents 1000+ GeoIP elements being returned - self.verify_nftables(nftables_search, 'ip filter', args='-t') + self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t') def test_groups(self): hostmap_path = ['system', 'static-host-mapping', 'host-name'] @@ -128,7 +122,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) + self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) self.cli_commit() nftables_search = [ @@ -143,7 +137,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['192.0.2.10, 192.0.2.11 }'], ['ip saddr @D_smoketest_domain', 'return'] ] - self.verify_nftables(nftables_search, 'ip filter') + self.verify_nftables(nftables_search, 'ip vyos_filter') self.cli_delete(['system', 'static-host-mapping']) self.cli_commit() @@ -160,7 +154,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) + self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) self.cli_commit() @@ -178,7 +172,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['elements = { 53, 123 }'] ] - self.verify_nftables(nftables_search, 'ip filter') + self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv4_basic_rules(self): name = 'smoketest' @@ -215,25 +209,31 @@ 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', '5', 'inbound-interface', interface]) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', interface]) - self.cli_set(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() nftables_search = [ [f'iifname "{interface}"', f'jump NAME_{name}'], - ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15','return'], - ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'], - ['tcp dport { 22 }', 'limit rate 5/minute', 'return'], + ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15', 'return'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'], + ['tcp dport 22', 'limit rate 5/minute', 'return'], ['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'], - [f'tcp flags & syn == syn tcp option maxseg size {mss_range}'], + ['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}', f'iifname "{interface}"'], + ['meta l4proto gre', f'oifname "{interface}"', 'return'] ] - self.verify_nftables(nftables_search, 'ip filter') + self.verify_nftables(nftables_search, 'ip vyos_filter') - def test_ipv4_packet_length(self): - name = 'smoketest-plen' + def test_ipv4_advanced(self): + name = 'smoketest-adv' + name2 = 'smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) @@ -243,23 +243,36 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '64']) self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '512']) self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) + 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(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() nftables_search = [ [f'iifname "{interface}"', f'jump NAME_{name}'], - ['ip length { 64, 512, 1024 }', 'return'], - ['ip length { 1-30000 }', 'ip length != { 60000-65535 }', 'return'], - [f'log prefix "[{name}-default-D]" drop'] + ['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'], + ['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 filter') + self.verify_nftables(nftables_search, 'ip vyos_filter') def test_ipv6_basic_rules(self): name = 'v6-smoketest' @@ -277,22 +290,29 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject']) 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', '2', 'inbound-interface', interface]) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'ipv6-name', name]) + 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', 'ipv6-name', name, 'rule', '3', 'outbound-interface', interface]) + + self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) self.cli_commit() nftables_search = [ [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'], + ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], + ['meta l4proto gre', f'oifname "{interface}"', 'return'], ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop'] ] - self.verify_nftables(nftables_search, 'ip6 filter') + self.verify_nftables(nftables_search, 'ip6 vyos_filter') - def test_ipv6_packet_length(self): - name = 'v6-smoketest-plen' + 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']) @@ -302,23 +322,36 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '65']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '513']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '1025']) + self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '18']) + self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '53']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'action', 'accept']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length', '1-1999']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length-exclude', '60000-65535']) + 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(['interfaces', 'ethernet', interface, 'firewall', 'in', 'ipv6-name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) self.cli_commit() nftables_search = [ [f'iifname "{interface}"', f'jump NAME6_{name}'], - ['ip6 length { 65, 513, 1025 }', 'return'], - ['ip6 length { 1-1999 }', 'ip6 length != { 60000-65535 }', 'return'], - [f'log prefix "[{name}-default-D]"', 'drop'] + ['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'], + ['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 filter') + self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_state_policy(self): self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept']) @@ -328,11 +361,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_commit() chains = { - 'ip filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'], - 'ip6 filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] + 'ip vyos_filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'], + 'ip6 vyos_filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] } - for table in ['ip filter', 'ip6 filter']: + for table in ['ip vyos_filter', 'ip6 vyos_filter']: for chain in chains[table]: nftables_output = cmd(f'sudo nft list chain {table} {chain}') self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output) @@ -356,20 +389,20 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'established', 'enable']) self.cli_set(['firewall', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) - self.cli_set(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() nftables_search = [ [f'iifname "{interface}"', f'jump NAME_{name}'], ['ct state { established, related }', 'return'], - ['ct state { invalid }', 'reject'], - ['ct state { new }', 'ct status { dnat }', 'return'], - ['ct state { established, new }', 'ct status { snat }', 'return'], + ['ct state invalid', 'reject'], + ['ct state new', 'ct status dnat', 'return'], + ['ct state { established, new }', 'ct status snat', 'return'], ['drop', f'comment "{name} default-action drop"'] ] - self.verify_nftables(nftables_search, 'ip filter') + self.verify_nftables(nftables_search, 'ip vyos_filter') def test_sysfs(self): for name, conf in sysfs_config.items(): @@ -388,5 +421,35 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): with open(path, 'r') as f: self.assertNotEqual(f.read().strip(), conf['default'], msg=path) + def test_zone_basic(self): + self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) + + self.cli_commit() + + nftables_search = [ + ['chain VZONE_smoketest-eth0'], + ['chain VZONE_smoketest-local_IN'], + ['chain VZONE_smoketest-local_OUT'], + ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], + ['jump VZONE_smoketest-local_IN'], + ['jump VZONE_smoketest-local_OUT'], + ['iifname "eth0"', 'jump NAME_smoketest'], + ['oifname "eth0"', 'jump NAME_smoketest'] + ] + + nftables_output = cmd('sudo nft list table ip vyos_filter') + + 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(matched) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 05d2ae5f5..ed611062a 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 @@ -119,15 +120,13 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): cls._base_path = ['interfaces', 'ethernet'] cls._mirror_interfaces = ['dum21354'] - # we need to filter out VLAN interfaces identified by a dot (.) - # in their name - just in case! + # We only test on physical interfaces and not VLAN (sub-)interfaces if 'TEST_ETH' in os.environ: tmp = os.environ['TEST_ETH'].split() cls._interfaces = tmp else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._interfaces.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) cls._macs = {} for interface in cls._interfaces: @@ -185,6 +184,39 @@ 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) + + 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 c5db066db..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' @@ -150,10 +136,10 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth1"', 'tcp dport { 4545 } ip6 saddr 2001:db8:2222::/64 tcp sport { 8080 } dnat to 2001:db8:1111::1:5555'] + ['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 @@ -219,10 +208,10 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64 tcp dport { 9999 } tcp sport { 8080 } snat to 2001:db8:1111::1:80'] + ['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_policy.py b/smoketest/scripts/cli/test_policy.py index 3d37d22ae..2166e63ec 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -698,6 +698,184 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): for rule in test_range: tmp = f'ip prefix-list {prefix_list} seq {rule} permit {prefix} le {rule}' self.assertIn(tmp, config) + def test_route_map_community_set(self): + test_data = { + "community-configuration": { + "rule": { + "10": { + "action": "permit", + "set": { + "community": { + "replace": [ + "65000:10", + "65001:11" + ] + }, + "extcommunity": { + "bandwidth": "200", + "rt": [ + "65000:10", + "192.168.0.1:11" + ], + "soo": [ + "192.168.0.1:11", + "65000:10" + ] + }, + "large-community": { + "replace": [ + "65000:65000:10", + "65000:65000:11" + ] + } + } + }, + "20": { + "action": "permit", + "set": { + "community": { + "add": [ + "65000:10", + "65001:11" + ] + }, + "extcommunity": { + "bandwidth": "200", + "bandwidth-non-transitive": {} + }, + "large-community": { + "add": [ + "65000:65000:10", + "65000:65000:11" + ] + } + } + }, + "30": { + "action": "permit", + "set": { + "community": { + "none": {} + }, + "extcommunity": { + "none": {} + }, + "large-community": { + "none": {} + } + } + } + } + } + } + for route_map, route_map_config in test_data.items(): + path = base_path + ['route-map', route_map] + self.cli_set(path + ['description', f'VyOS ROUTE-MAP {route_map}']) + if 'rule' not in route_map_config: + continue + + for rule, rule_config in route_map_config['rule'].items(): + if 'action' in rule_config: + self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) + if 'set' in rule_config: + + #Add community in configuration + if 'community' in rule_config['set']: + if 'none' in rule_config['set']['community']: + self.cli_set(path + ['rule', rule, 'set', 'community', 'none']) + else: + community_path = path + ['rule', rule, 'set', 'community'] + if 'add' in rule_config['set']['community']: + for community_unit in rule_config['set']['community']['add']: + self.cli_set(community_path + ['add', community_unit]) + if 'replace' in rule_config['set']['community']: + for community_unit in rule_config['set']['community']['replace']: + self.cli_set(community_path + ['replace', community_unit]) + + #Add large-community in configuration + if 'large-community' in rule_config['set']: + if 'none' in rule_config['set']['large-community']: + self.cli_set(path + ['rule', rule, 'set', 'large-community', 'none']) + else: + community_path = path + ['rule', rule, 'set', 'large-community'] + if 'add' in rule_config['set']['large-community']: + for community_unit in rule_config['set']['large-community']['add']: + self.cli_set(community_path + ['add', community_unit]) + if 'replace' in rule_config['set']['large-community']: + for community_unit in rule_config['set']['large-community']['replace']: + self.cli_set(community_path + ['replace', community_unit]) + + #Add extcommunity in configuration + if 'extcommunity' in rule_config['set']: + if 'none' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'none']) + else: + if 'bandwidth' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'bandwidth', rule_config['set']['extcommunity']['bandwidth']]) + if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: + self.cli_set(path + ['rule', rule, 'set','extcommunity', 'bandwidth-non-transitive']) + if 'rt' in rule_config['set']['extcommunity']: + for community_unit in rule_config['set']['extcommunity']['rt']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity','rt',community_unit]) + if 'soo' in rule_config['set']['extcommunity']: + for community_unit in rule_config['set']['extcommunity']['soo']: + self.cli_set(path + ['rule', rule, 'set', 'extcommunity','soo',community_unit]) + self.cli_commit() + + for route_map, route_map_config in test_data.items(): + if 'rule' not in route_map_config: + continue + for rule, rule_config in route_map_config['rule'].items(): + name = f'route-map {route_map} {rule_config["action"]} {rule}' + config = self.getFRRconfig(name) + self.assertIn(name, config) + + if 'set' in rule_config: + #Check community + if 'community' in rule_config['set']: + if 'none' in rule_config['set']['community']: + tmp = f'set community none' + self.assertIn(tmp, config) + if 'replace' in rule_config['set']['community']: + values = ' '.join(rule_config['set']['community']['replace']) + tmp = f'set community {values}' + self.assertIn(tmp, config) + if 'add' in rule_config['set']['community']: + values = ' '.join(rule_config['set']['community']['add']) + tmp = f'set community {values} additive' + self.assertIn(tmp, config) + #Check large-community + if 'large-community' in rule_config['set']: + if 'none' in rule_config['set']['large-community']: + tmp = f'set large-community none' + self.assertIn(tmp, config) + if 'replace' in rule_config['set']['large-community']: + values = ' '.join(rule_config['set']['large-community']['replace']) + tmp = f'set large-community {values}' + self.assertIn(tmp, config) + if 'add' in rule_config['set']['large-community']: + values = ' '.join(rule_config['set']['large-community']['add']) + tmp = f'set large-community {values} additive' + self.assertIn(tmp, config) + #Check extcommunity + if 'extcommunity' in rule_config['set']: + if 'none' in rule_config['set']['extcommunity']: + tmp = 'set extcommunity none' + self.assertIn(tmp, config) + if 'bandwidth' in rule_config['set']['extcommunity']: + values = rule_config['set']['extcommunity']['bandwidth'] + tmp = f'set extcommunity bandwidth {values}' + if 'bandwidth-non-transitive' in rule_config['set']['extcommunity']: + tmp = tmp + ' non-transitive' + self.assertIn(tmp, config) + if 'rt' in rule_config['set']['extcommunity']: + values = ' '.join(rule_config['set']['extcommunity']['rt']) + tmp = f'set extcommunity rt {values}' + self.assertIn(tmp, config) + if 'soo' in rule_config['set']['extcommunity']: + values = ' '.join(rule_config['set']['extcommunity']['soo']) + tmp = f'set extcommunity soo {values}' + self.assertIn(tmp, config) def test_route_map(self): access_list = '50' @@ -845,13 +1023,9 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 'as-path-prepend-last-as' : '5', 'atomic-aggregate' : '', 'distance' : '110', - 'extcommunity-bw' : '20000', - 'extcommunity-rt' : '123:456', - 'extcommunity-soo' : '456:789', 'ipv6-next-hop-global' : '2001::1', 'ipv6-next-hop-local' : 'fe80::1', 'ip-next-hop' : '192.168.1.1', - 'large-community' : '100:200:300', 'local-preference' : '500', 'metric' : '150', 'metric-type' : 'type-1', @@ -1049,20 +1223,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['rule', rule, 'set', 'atomic-aggregate']) if 'distance' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'distance', rule_config['set']['distance']]) - if 'extcommunity-bw' in rule_config['set']: - self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'bandwidth', rule_config['set']['extcommunity-bw']]) - if 'extcommunity-rt' in rule_config['set']: - self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'rt', rule_config['set']['extcommunity-rt']]) - if 'extcommunity-soo' in rule_config['set']: - self.cli_set(path + ['rule', rule, 'set', 'extcommunity', 'soo', rule_config['set']['extcommunity-soo']]) if 'ipv6-next-hop-global' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'global', rule_config['set']['ipv6-next-hop-global']]) if 'ipv6-next-hop-local' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']]) if 'ip-next-hop' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']]) - if 'large-community' in rule_config['set']: - self.cli_set(path + ['rule', rule, 'set', 'large-community', rule_config['set']['large-community']]) if 'local-preference' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']]) if 'metric' in rule_config['set']: @@ -1236,20 +1402,12 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp += 'atomic-aggregate' elif 'distance' in rule_config['set']: tmp += 'distance ' + rule_config['set']['distance'] - elif 'extcommunity-bw' in rule_config['set']: - tmp += 'extcommunity bandwidth' + rule_config['set']['extcommunity-bw'] - elif 'extcommunity-rt' in rule_config['set']: - tmp += 'extcommunity rt' + rule_config['set']['extcommunity-rt'] - elif 'extcommunity-soo' in rule_config['set']: - tmp += 'extcommunity rt' + rule_config['set']['extcommunity-soo'] elif 'ip-next-hop' in rule_config['set']: tmp += 'ip next-hop ' + rule_config['set']['ip-next-hop'] elif 'ipv6-next-hop-global' in rule_config['set']: tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global'] elif 'ipv6-next-hop-local' in rule_config['set']: tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local'] - elif 'large-community' in rule_config['set']: - tmp += 'large-community ' + rule_config['set']['large-community'] elif 'local-preference' in rule_config['set']: tmp += 'local-preference ' + rule_config['set']['local-preference'] elif 'metric' in rule_config['set']: diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 534cfb082..046e385bb 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -53,7 +53,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['chain VYOS_PBR_smoketest'] ] - self.verify_nftables(nftables_search, 'ip filter', inverse=True) + self.verify_nftables(nftables_search, 'ip mangle', inverse=True) def verify_nftables(self, nftables_search, table, inverse=False): nftables_output = cmd(f'sudo nft list table {table}') @@ -127,7 +127,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): nftables_search = [ [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'], - ['tcp flags & (syn | ack) == syn', 'tcp dport { 8888 }', 'meta mark set ' + mark_hex] + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex] ] self.verify_nftables(nftables_search, 'ip mangle') @@ -136,7 +136,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): nftables6_search = [ [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'], - ['meta l4proto { tcp, udp }', 'th dport { 8888 }', 'meta mark set ' + mark_hex] + ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex] ] self.verify_nftables(nftables6_search, 'ip6 mangle') @@ -158,5 +158,81 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.assertTrue(matched) + def test_pbr_matching_criteria(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'udp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'action', 'drop']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '2', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'source', 'address', '198.51.100.0/24']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'state', 'new', 'enable']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'ttl', 'gt', '2']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '3', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'protocol', 'icmp']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'icmp', 'type-name', 'echo-request']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '128']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '1024-2048']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'log', 'enable']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'set', 'table', table_id]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '41']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '57-59']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'set', 'table', table_id]) + + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'protocol', 'udp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'action', 'drop']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '2', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'source', 'address', '2001:db8::0/64']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'state', 'new', 'enable']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'hop-limit', 'gt', '2']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '3', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'protocol', 'icmpv6']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'icmpv6', 'type', 'echo-request']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '128']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '1024-2048']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'log', 'enable']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'set', 'table', table_id]) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '61']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id]) + + self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) + self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6']) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(table_mark_offset - int(table_id)) + + # IPv4 + nftables_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'], + ['meta l4proto udp', 'drop'], + ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], + ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex], + ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta mark set ' + mark_hex], + ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables_search, 'ip mangle') + + # IPv6 + nftables6_search = [ + [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'], + ['meta l4proto udp', 'drop'], + ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], + ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex], + ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta mark set ' + mark_hex], + ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex] + ] + + self.verify_nftables(nftables6_search, 'ip6 mangle') + if __name__ == '__main__': unittest.main(verbosity=2) 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_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index ee4be0b37..c26028253 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -262,5 +262,51 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' isis bfd', tmp) self.assertIn(f' isis bfd profile {bfd_profile}', tmp) + def test_isis_07_segment_routing_configuration(self): + global_block_low = "100" + global_block_high = "199" + local_block_low = "200" + local_block_high = "299" + interface = 'lo' + maximum_stack_size = '5' + prefix_one = '192.168.0.1/32' + prefix_two = '192.168.0.2/32' + prefix_three = '192.168.0.3/32' + prefix_four = '192.168.0.4/32' + prefix_one_value = '1' + prefix_two_value = '2' + prefix_three_value = '60000' + prefix_four_value = '65000' + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'value', prefix_three_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'value', prefix_four_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'no-php-flag']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' segment-routing on', tmp) + self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp) + self.assertIn(f' segment-routing node-msd {maximum_stack_size}', tmp) + self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', tmp) + self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', tmp) + self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp) + self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py index 40b19fec7..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 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_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index e15ea478b..93bb761c1 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -14,8 +14,6 @@ # 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 logging -import sys import unittest from base_vyostest_shim import VyOSUnitTestSHIM @@ -23,15 +21,12 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.util import process_named_running -from vyos.util import cmd PROCESS_NAME = 'ospfd' base_path = ['protocols', 'ospf'] route_map = 'foo-bar-baz10' -log = logging.getLogger('TestProtocolsOSPF') - class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -210,25 +205,14 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map]) self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type]) - # enable FRR debugging to find the root cause of failing testcases - cmd('touch /tmp/vyos.frr.debug') - # commit changes self.cli_commit() - # disable FRR debugging - cmd('rm -f /tmp/vyos.frr.debug') - # Verify FRR ospfd configuration frrconfig = self.getFRRconfig('router ospf') - try: - self.assertIn(f'router ospf', frrconfig) - for protocol in redistribute: - self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) - except: - log.debug(frrconfig) - log.debug(cmd('sudo cat /tmp/vyos-configd-script-stdout')) - self.fail('Now we can hopefully see why OSPF fails!') + self.assertIn(f'router ospf', frrconfig) + for protocol in redistribute: + self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) def test_ospf_08_virtual_link(self): networks = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] @@ -396,6 +380,44 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' network {network} area {area}', frrconfig) self.assertIn(f' area {area} export-list {acl}', frrconfig) + + def test_ospf_14_segment_routing_configuration(self): + global_block_low = "100" + global_block_high = "199" + local_block_low = "200" + local_block_high = "299" + interface = 'lo' + maximum_stack_size = '5' + prefix_one = '192.168.0.1/32' + prefix_two = '192.168.0.2/32' + prefix_one_value = '1' + prefix_two_value = '2' + + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low]) + self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'low-label-value', local_block_low]) + self.cli_set(base_path + ['segment-routing', 'local-block', 'high-label-value', local_block_high]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'value', prefix_one_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_one, 'index', 'explicit-null']) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'value', prefix_two_value]) + self.cli_set(base_path + ['segment-routing', 'prefix', prefix_two, 'index', 'no-php-flag']) + + # Commit all changes + self.cli_commit() + + # Verify all changes + + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig) + self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig) + self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig) + self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig) + + self.skipTest('https://github.com/FRRouting/frr/issues/12007') + self.assertIn(f' segment-routing on', frrconfig) + + if __name__ == '__main__': - logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index d471eeaed..dcf2bcefe 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -77,9 +77,9 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['listen-interface', tmp]) self.cli_set(base_path + ['direction', 'in']) - self.cli_set(base_path + ['threshold', 'fps', fps]) - self.cli_set(base_path + ['threshold', 'pps', pps]) - self.cli_set(base_path + ['threshold', 'mbps', mbps]) + self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) + self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) + self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) # commit changes self.cli_commit() 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_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py index c1c4044e6..ed486c3b9 100755 --- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -60,6 +60,7 @@ class TestMonitoringTelegraf(VyOSUnitTestSHIM.TestCase): self.assertIn(f' token = "$INFLUX_TOKEN"', config) self.assertIn(f'urls = ["{url}:{port}"]', config) self.assertIn(f'bucket = "{bucket}"', config) + self.assertIn(f'[[inputs.exec]]', config) for input in inputs: self.assertIn(input, config) 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/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py deleted file mode 100755 index 2c580e2f1..000000000 --- a/smoketest/scripts/cli/test_zone_policy.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.util import cmd - -class TestZonePolicy(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestZonePolicy, cls).setUpClass() - cls.cli_set(cls, ['firewall', 'name', 'smoketest', 'default-action', 'drop']) - - @classmethod - def tearDownClass(cls): - cls.cli_delete(cls, ['firewall']) - super(TestZonePolicy, cls).tearDownClass() - - def tearDown(self): - self.cli_delete(['zone-policy']) - self.cli_commit() - - def test_basic_zone(self): - self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'interface', 'eth0']) - self.cli_set(['zone-policy', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) - self.cli_set(['zone-policy', 'zone', 'smoketest-local', 'local-zone']) - self.cli_set(['zone-policy', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) - - self.cli_commit() - - nftables_search = [ - ['chain VZONE_smoketest-eth0'], - ['chain VZONE_smoketest-local_IN'], - ['chain VZONE_smoketest-local_OUT'], - ['oifname { "eth0" }', 'jump VZONE_smoketest-eth0'], - ['jump VZONE_smoketest-local_IN'], - ['jump VZONE_smoketest-local_OUT'], - ['iifname { "eth0" }', 'jump NAME_smoketest'], - ['oifname { "eth0" }', 'jump NAME_smoketest'] - ] - - nftables_output = cmd('sudo nft list table ip filter') - - 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(matched) - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/src/completion/list_consoles.sh b/src/completion/list_consoles.sh new file mode 100755 index 000000000..52278c4cb --- /dev/null +++ b/src/completion/list_consoles.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# For lines like `aliases "foo";`, regex matches everything between the quotes +grep -oP '(?<=aliases ").+(?=";)' /run/conserver/conserver.cf
\ No newline at end of file diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py deleted file mode 100755 index ab1c69259..000000000 --- a/src/conf_mode/firewall-interface.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os -import re - -from sys import argv -from sys import exit - -from vyos.config import Config -from vyos.configdict import leaf_node_changed -from vyos.ifconfig import Section -from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -NFT_CHAINS = { - 'in': 'VYOS_FW_FORWARD', - 'out': 'VYOS_FW_FORWARD', - 'local': 'VYOS_FW_LOCAL' -} -NFT6_CHAINS = { - 'in': 'VYOS_FW6_FORWARD', - 'out': 'VYOS_FW6_FORWARD', - 'local': 'VYOS_FW6_LOCAL' -} - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - ifname = argv[1] - ifpath = Section.get_config_path(ifname) - if_firewall_path = f'interfaces {ifpath} firewall' - - if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - if_firewall['ifname'] = ifname - if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - return if_firewall - -def verify_chain(table, chain): - # Verify firewall applied - code = run(f'nft list chain {table} {chain}') - return code == 0 - -def verify(if_firewall): - # bail out early - looks like removal from running config - if not if_firewall: - return None - - for direction in ['in', 'out', 'local']: - if direction in if_firewall: - if 'name' in if_firewall[direction]: - name = if_firewall[direction]['name'] - - if 'name' not in if_firewall['firewall']: - raise ConfigError('Firewall name not configured') - - if name not in if_firewall['firewall']['name']: - raise ConfigError(f'Invalid firewall name "{name}"') - - if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'): - raise ConfigError('Firewall did not apply') - - if 'ipv6_name' in if_firewall[direction]: - name = if_firewall[direction]['ipv6_name'] - - if 'ipv6_name' not in if_firewall['firewall']: - raise ConfigError('Firewall ipv6-name not configured') - - if name not in if_firewall['firewall']['ipv6_name']: - raise ConfigError(f'Invalid firewall ipv6-name "{name}"') - - if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'): - raise ConfigError('Firewall did not apply') - - return None - -def generate(if_firewall): - return None - -def cleanup_rule(table, chain, prefix, ifname, new_name=None): - results = cmd(f'nft -a list chain {table} {chain}').split("\n") - retval = None - for line in results: - if f'{prefix}ifname "{ifname}"' in line: - if new_name and f'jump {new_name}' in line: - # new_name is used to clear rules for any previously referenced chains - # returns true when rule exists and doesn't need to be created - retval = True - continue - - handle_search = re.search('handle (\d+)', line) - if handle_search: - run(f'nft delete rule {table} {chain} handle {handle_search[1]}') - return retval - -def state_policy_handle(table, chain): - # Find any state-policy rule to ensure interface rules are only inserted afterwards - results = cmd(f'nft -a list chain {table} {chain}').split("\n") - for line in results: - if 'jump VYOS_STATE_POLICY' in line: - handle_search = re.search('handle (\d+)', line) - if handle_search: - return handle_search[1] - return None - -def apply(if_firewall): - ifname = if_firewall['ifname'] - - for direction in ['in', 'out', 'local']: - chain = NFT_CHAINS[direction] - ipv6_chain = NFT6_CHAINS[direction] - if_prefix = 'i' if direction in ['in', 'local'] else 'o' - - name = dict_search_args(if_firewall, direction, 'name') - if name: - rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}') - - if not rule_exists: - rule_action = 'insert' - rule_prefix = '' - - handle = state_policy_handle('ip filter', chain) - if handle: - rule_action = 'add' - rule_prefix = f'position {handle}' - - run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}') - else: - cleanup_rule('ip filter', chain, if_prefix, ifname) - - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if ipv6_name: - rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}') - - if not rule_exists: - rule_action = 'insert' - rule_prefix = '' - - handle = state_policy_handle('ip6 filter', ipv6_chain) - if handle: - rule_action = 'add' - rule_prefix = f'position {handle}' - - run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}') - else: - cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f0ea1a1e5..cbd9cbe90 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,6 +26,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff +# from vyos.configverify import verify_interface_exists from vyos.firewall import geoip_update from vyos.firewall import get_ips_domains_dict from vyos.firewall import nft_add_set_elements @@ -38,7 +39,7 @@ from vyos.util import cmd from vyos.util import dict_search_args from vyos.util import dict_search_recursive from vyos.util import process_named_running -from vyos.util import run +from vyos.util import rc_cmd from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -47,7 +48,6 @@ airbag.enable() policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py' nftables_conf = '/run/nftables.conf' -nftables_defines_conf = '/run/nftables_defines.conf' sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'}, @@ -63,28 +63,6 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -preserve_chains = [ - 'INPUT', - 'FORWARD', - 'OUTPUT', - 'VYOS_FW_FORWARD', - 'VYOS_FW_LOCAL', - 'VYOS_FW_OUTPUT', - 'VYOS_POST_FW', - 'VYOS_FRAG_MARK', - 'VYOS_FW6_FORWARD', - 'VYOS_FW6_LOCAL', - 'VYOS_FW6_OUTPUT', - 'VYOS_POST_FW6', - 'VYOS_FRAG6_MARK' -] - -nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] -nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] - valid_groups = [ 'address_group', 'domain_group', @@ -97,16 +75,6 @@ nested_group_types = [ 'port_group', 'ipv6_address_group', 'ipv6_network_group' ] -group_set_prefix = { - 'A_': 'address_group', - 'A6_': 'ipv6_address_group', - 'D_': 'domain_group', - 'M_': 'mac_group', - 'N_': 'network_group', - 'N6_': 'ipv6_network_group', - 'P_': 'port_group' -} - snmp_change_type = { 'unknown': 0, 'add': 1, @@ -117,51 +85,6 @@ snmp_event_source = 1 snmp_trap_mib = 'VYATTA-TRAP-MIB' snmp_trap_name = 'mgmtEventTrap' -def get_firewall_interfaces(conf): - out = {} - interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - def find_interfaces(iftype_conf, output={}, prefix=''): - for ifname, if_conf in iftype_conf.items(): - if 'firewall' in if_conf: - output[prefix + ifname] = if_conf['firewall'] - for vif in ['vif', 'vif_s', 'vif_c']: - if vif in if_conf: - output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.')) - return output - for iftype, iftype_conf in interfaces.items(): - out.update(find_interfaces(iftype_conf)) - return out - -def get_firewall_zones(conf): - used_v4 = [] - used_v6 = [] - zone_policy = conf.get_config_dict(['zone-policy'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - if 'zone' in zone_policy: - for zone, zone_conf in zone_policy['zone'].items(): - if 'from' in zone_conf: - for from_zone, from_conf in zone_conf['from'].items(): - name = dict_search_args(from_conf, 'firewall', 'name') - if name: - used_v4.append(name) - - ipv6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') - if ipv6_name: - used_v6.append(ipv6_name) - - if 'intra_zone_filtering' in zone_conf: - name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'name') - if name: - used_v4.append(name) - - ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name') - if ipv6_name: - used_v6.append(ipv6_name) - - return {'name': used_v4, 'ipv6_name': used_v6} - def geoip_updated(conf, firewall): diff = get_config_diff(conf) node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True) @@ -215,6 +138,9 @@ def get_config(config=None): if tmp in default_values: del default_values[tmp] + if 'zone' in default_values: + del default_values['zone'] + firewall = dict_merge(default_values, firewall) # Merge in defaults for IPv4 ruleset @@ -231,9 +157,12 @@ def get_config(config=None): firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, firewall['ipv6_name'][ipv6_name]) + if 'zone' in firewall: + default_values = defaults(base + ['zone']) + for zone in firewall['zone']: + firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone]) + firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) - firewall['interfaces'] = get_firewall_interfaces(conf) - firewall['zone_policy'] = get_firewall_zones(conf) if 'config_trap' in firewall and firewall['config_trap'] == 'enable': diff = get_config_diff(conf) @@ -250,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"') @@ -358,109 +301,111 @@ def verify(firewall): for name in ['name', 'ipv6_name']: if name in firewall: for name_id, name_conf in firewall[name].items(): - if name_id in preserve_chains: - raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS') - - if name_id.startswith("VZONE"): - raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix') + if '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') - for ifname, if_firewall in firewall['interfaces'].items(): - for direction in ['in', 'out', 'local']: - name = dict_search_args(if_firewall, direction, 'name') - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') + if 'interface' in firewall: + for ifname, if_firewall in firewall['interface'].items(): + # verify ifname needs to be disabled, dynamic devices come up later + # verify_interface_exists(ifname) - if name and dict_search_args(firewall, 'name', name) == None: - raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}') + for direction in ['in', 'out', 'local']: + name = dict_search_args(if_firewall, direction, 'name') + ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: - raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}') + if name and dict_search_args(firewall, 'name', name) == None: + raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}') - for fw_name, used_names in firewall['zone_policy'].items(): - for name in used_names: - if dict_search_args(firewall, fw_name, name) == None: - raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') + if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: + raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}') - return None + local_zone = False + zone_interfaces = [] -def cleanup_commands(firewall): - commands = [] - commands_chains = [] - commands_sets = [] - for table in ['ip filter', 'ip6 filter']: - name_node = 'name' if table == 'ip filter' else 'ipv6_name' - chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX - state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6' - iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains - - geoip_list = [] - if firewall['geoip_updated']: - geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name' - geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or [] - - json_str = cmd(f'nft -t -j list table {table}') - obj = loads(json_str) - - if 'nftables' not in obj: - continue - - for item in obj['nftables']: - if 'chain' in item: - chain = item['chain']['name'] - if chain in preserve_chains or chain.startswith("VZONE"): - continue + if 'zone' in firewall: + for zone, zone_conf in firewall['zone'].items(): + if 'local_zone' not in zone_conf and 'interface' not in zone_conf: + raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') + + if 'local_zone' in zone_conf: + if local_zone: + raise ConfigError('There cannot be multiple local zones') + if 'interface' in zone_conf: + raise ConfigError('Local zone cannot have interfaces assigned') + if 'intra_zone_filtering' in zone_conf: + raise ConfigError('Local zone cannot use intra-zone-filtering') + local_zone = True - if chain == state_chain: - command = 'delete' if 'state_policy' not in firewall else 'flush' - commands_chains.append(f'{command} chain {table} {chain}') - elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None: - commands.append(f'flush chain {table} {chain}') - else: - commands_chains.append(f'delete chain {table} {chain}') + if 'interface' in zone_conf: + found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] - if 'rule' in item: - rule = item['rule'] - chain = rule['chain'] - handle = rule['handle'] + if found_duplicates: + raise ConfigError(f'Interfaces cannot be assigned to multiple zones') - if chain in iface_chains: - target, _ = next(dict_search_recursive(rule['expr'], 'target')) + zone_interfaces += zone_conf['interface'] - if target == state_chain and 'state_policy' not in firewall: - commands.append(f'delete rule {table} {chain} handle {handle}') + if 'intra_zone_filtering' in zone_conf: + intra_zone = zone_conf['intra_zone_filtering'] - if target.startswith(chain_prefix): - if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None: - commands.append(f'delete rule {table} {chain} handle {handle}') + if len(intra_zone) > 1: + raise ConfigError('Only one intra-zone-filtering action must be specified') - if 'set' in item: - set_name = item['set']['name'] + if 'firewall' in intra_zone: + v4_name = dict_search_args(intra_zone, 'firewall', 'name') + if v4_name and not dict_search_args(firewall, 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') - if set_name.startswith('GEOIP_CC_') and set_name in geoip_list: - commands_sets.append(f'delete set {table} {set_name}') - continue + v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') + if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - if set_name.startswith("RECENT_"): - commands_sets.append(f'delete set {table} {set_name}') - continue + if not v4_name and not v6_name: + raise ConfigError('No firewall names specified for intra-zone-filtering') + + if 'from' in zone_conf: + for from_zone, from_conf in zone_conf['from'].items(): + if from_zone not in firewall['zone']: + raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') + + v4_name = dict_search_args(from_conf, 'firewall', 'name') + if v4_name and not dict_search_args(firewall, 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') + + v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') + if v6_name and not dict_search_args(firewall, 'ipv6_name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - for prefix, group_type in group_set_prefix.items(): - if set_name.startswith(prefix): - group_name = set_name.replace(prefix, "", 1) - if dict_search_args(firewall, 'group', group_type, group_name) != None: - commands_sets.append(f'flush set {table} {set_name}') - else: - commands_sets.append(f'delete set {table} {set_name}') - return commands + commands_chains + commands_sets + return None def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True - else: - firewall['cleanup_commands'] = cleanup_commands(firewall) + + if 'zone' in firewall: + for local_zone, local_zone_conf in firewall['zone'].items(): + if 'local_zone' not in local_zone_conf: + continue + + local_zone_conf['from_local'] = {} + + for zone, zone_conf in firewall['zone'].items(): + if zone == local_zone or 'from' not in zone_conf: + continue + if local_zone in zone_conf['from']: + local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] render(nftables_conf, 'firewall/nftables.j2', firewall) return None @@ -521,26 +466,16 @@ def post_apply_trap(firewall): cmd(base_cmd + ' '.join(objects)) -def state_policy_rule_exists(): - # Determine if state policy rules already exist in nft - search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD') - return 'VYOS_STATE_POLICY' in search_str - def resync_policy_route(): # Update policy route as firewall groups were updated - tmp = run(policy_route_conf_script) + tmp, out = rc_cmd(policy_route_conf_script) if tmp > 0: - Warning('Failed to re-apply policy route configuration!') + Warning(f'Failed to re-apply policy route configuration! {out}') def apply(firewall): - if 'first_install' in firewall: - run('nfct helper add rpc inet tcp') - run('nfct helper add rpc inet udp') - run('nfct helper add tns inet tcp') - - install_result = run(f'nft -f {nftables_conf}') + install_result, output = rc_cmd(f'nft -f {nftables_conf}') if install_result == 1: - raise ConfigError('Failed to apply firewall') + raise ConfigError(f'Failed to apply firewall: {output}') # set firewall group domain-group xxx if 'group' in firewall: @@ -563,13 +498,6 @@ def apply(firewall): else: call('systemctl stop vyos-domain-group-resolve.service') - if 'state_policy' in firewall and not state_policy_rule_exists(): - for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']: - cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY') - - for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']: - cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6') - apply_sysfs(firewall) if firewall['policy_resync']: diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index ef745d737..8155f36c2 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -55,6 +55,7 @@ from vyos.util import chown from vyos.util import cmd from vyos.util import dict_search from vyos.util import dict_search_args +from vyos.util import is_list_equal from vyos.util import makedir from vyos.util import read_file from vyos.util import write_file @@ -274,7 +275,7 @@ def verify(openvpn): elif v6remAddr and not v6loAddr: raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"') - if (v4loAddr == v4remAddr) or (v6remAddr == v4remAddr): + if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr): raise ConfigError('"local-address" and "remote-address" cannot be the same') if dict_search('local_host', openvpn) in dict_search('local_address', openvpn): 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/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 97b3a6396..a14a992ae 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -116,7 +116,7 @@ def generate(wwan): # disconnect - e.g. happens during RF signal loss. The script watches every # WWAN interface - so there is only one instance. if not os.path.exists(cron_script): - write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py\n') return None 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/policy.py b/src/conf_mode/policy.py index 3008a20e0..a0d288e91 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -23,8 +23,42 @@ from vyos.util import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag + airbag.enable() + +def community_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in community and large community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ('replace' in actions or 'add' in actions): + return False + if 'replace' in actions and 'add' in actions: + return False + if ('delete' in actions) and ('none' in actions or 'replace' in actions): + return False + return True + + +def extcommunity_action_compatibility(actions: dict) -> bool: + """ + Check compatibility of values in extended community sections + :param actions: dictionary with community + :type actions: dict + :return: true if compatible, false if not + :rtype: bool + """ + if ('none' in actions) and ( + 'rt' in actions or 'soo' in actions or 'bandwidth' in actions or 'bandwidth_non_transitive' in actions): + return False + if ('bandwidth_non_transitive' in actions) and ('bandwidth' not in actions): + return False + return True + def routing_policy_find(key, dictionary): # Recursively traverse a dictionary and extract the value assigned to # a given key as generator object. This is made for routing policies, @@ -46,6 +80,7 @@ def routing_policy_find(key, dictionary): for result in routing_policy_find(key, d): yield result + def get_config(config=None): if config: conf = config @@ -53,7 +88,8 @@ def get_config(config=None): conf = Config() base = ['policy'] - policy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, + policy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) # We also need some additional information from the config, prefix-lists @@ -67,12 +103,14 @@ def get_config(config=None): policy = dict_merge(tmp, policy) return policy + def verify(policy): if not policy: return None for policy_type in ['access_list', 'access_list6', 'as_path_list', - 'community_list', 'extcommunity_list', 'large_community_list', + 'community_list', 'extcommunity_list', + 'large_community_list', 'prefix_list', 'prefix_list6', 'route_map']: # Bail out early and continue with next policy type if policy_type not in policy: @@ -97,15 +135,18 @@ def verify(policy): if 'source' not in rule_config: raise ConfigError(f'A source {mandatory_error}') - if int(instance) in range(100, 200) or int(instance) in range(2000, 2700): + if int(instance) in range(100, 200) or int( + instance) in range(2000, 2700): if 'destination' not in rule_config: - raise ConfigError(f'A destination {mandatory_error}') + raise ConfigError( + f'A destination {mandatory_error}') if policy_type == 'access_list6': if 'source' not in rule_config: raise ConfigError(f'A source {mandatory_error}') - if policy_type in ['as_path_list', 'community_list', 'extcommunity_list', + if policy_type in ['as_path_list', 'community_list', + 'extcommunity_list', 'large_community_list']: if 'regex' not in rule_config: raise ConfigError(f'A regex {mandatory_error}') @@ -115,10 +156,10 @@ def verify(policy): raise ConfigError(f'A prefix {mandatory_error}') if rule_config in entries: - raise ConfigError(f'Rule "{rule}" contains a duplicate prefix definition!') + raise ConfigError( + f'Rule "{rule}" contains a duplicate prefix definition!') entries.append(rule_config) - # route-maps tend to be a bit more complex so they get their own verify() section if 'route_map' in policy: for route_map, route_map_config in policy['route_map'].items(): @@ -127,19 +168,23 @@ def verify(policy): for rule, rule_config in route_map_config['rule'].items(): # Specified community-list must exist - tmp = dict_search('match.community.community_list', rule_config) + tmp = dict_search('match.community.community_list', + rule_config) if tmp and tmp not in policy.get('community_list', []): raise ConfigError(f'community-list {tmp} does not exist!') # Specified extended community-list must exist tmp = dict_search('match.extcommunity', rule_config) if tmp and tmp not in policy.get('extcommunity_list', []): - raise ConfigError(f'extcommunity-list {tmp} does not exist!') + raise ConfigError( + f'extcommunity-list {tmp} does not exist!') # Specified large-community-list must exist - tmp = dict_search('match.large_community.large_community_list', rule_config) + tmp = dict_search('match.large_community.large_community_list', + rule_config) if tmp and tmp not in policy.get('large_community_list', []): - raise ConfigError(f'large-community-list {tmp} does not exist!') + raise ConfigError( + f'large-community-list {tmp} does not exist!') # Specified prefix-list must exist tmp = dict_search('match.ip.address.prefix_list', rule_config) @@ -147,49 +192,87 @@ def verify(policy): raise ConfigError(f'prefix-list {tmp} does not exist!') # Specified prefix-list must exist - tmp = dict_search('match.ipv6.address.prefix_list', rule_config) + tmp = dict_search('match.ipv6.address.prefix_list', + rule_config) if tmp and tmp not in policy.get('prefix_list6', []): raise ConfigError(f'prefix-list6 {tmp} does not exist!') - + # Specified access_list6 in nexthop must exist - tmp = dict_search('match.ipv6.nexthop.access_list', rule_config) + tmp = dict_search('match.ipv6.nexthop.access_list', + rule_config) if tmp and tmp not in policy.get('access_list6', []): raise ConfigError(f'access_list6 {tmp} does not exist!') # Specified prefix-list6 in nexthop must exist - tmp = dict_search('match.ipv6.nexthop.prefix_list', rule_config) + tmp = dict_search('match.ipv6.nexthop.prefix_list', + rule_config) if tmp and tmp not in policy.get('prefix_list6', []): raise ConfigError(f'prefix-list6 {tmp} does not exist!') + tmp = dict_search('set.community.delete', rule_config) + if tmp and tmp not in policy.get('community_list', []): + raise ConfigError(f'community-list {tmp} does not exist!') + + tmp = dict_search('set.large_community.delete', + rule_config) + if tmp and tmp not in policy.get('large_community_list', []): + raise ConfigError( + f'large-community-list {tmp} does not exist!') + + if 'set' in rule_config: + rule_action = rule_config['set'] + if 'community' in rule_action: + if not community_action_compatibility( + rule_action['community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in community') + if 'large_community' in rule_action: + if not community_action_compatibility( + rule_action['large_community']): + raise ConfigError( + f'Unexpected combination between action replace, add, delete or none in large-community') + if 'extcommunity' in rule_action: + if not extcommunity_action_compatibility( + rule_action['extcommunity']): + raise ConfigError( + f'Unexpected combination between none, rt, soo, bandwidth, bandwidth-non-transitive in extended-community') # When routing protocols are active some use prefix-lists, route-maps etc. # to apply the systems routing policy to the learned or redistributed routes. # When the "routing policy" changes and policies, route-maps etc. are deleted, # it is our responsibility to verify that the policy can not be deleted if it # is used by any routing protocol if 'protocols' in policy: - for policy_type in ['access_list', 'access_list6', 'as_path_list', 'community_list', - 'extcommunity_list', 'large_community_list', 'prefix_list', 'route_map']: + for policy_type in ['access_list', 'access_list6', 'as_path_list', + 'community_list', + 'extcommunity_list', 'large_community_list', + 'prefix_list', 'route_map']: if policy_type in policy: - for policy_name in list(set(routing_policy_find(policy_type, policy['protocols']))): + for policy_name in list(set(routing_policy_find(policy_type, + policy[ + 'protocols']))): found = False if policy_name in policy[policy_type]: found = True # BGP uses prefix-list for selecting both an IPv4 or IPv6 AFI related # list - we need to go the extra mile here and check both prefix-lists - if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in policy['prefix_list6']: + if policy_type == 'prefix_list' and 'prefix_list6' in policy and policy_name in \ + policy['prefix_list6']: found = True if not found: - tmp = policy_type.replace('_','-') - raise ConfigError(f'Can not delete {tmp} "{policy_name}", still in use!') + tmp = policy_type.replace('_', '-') + raise ConfigError( + f'Can not delete {tmp} "{policy_name}", still in use!') return None + def generate(policy): if not policy: return None policy['new_frr_config'] = render_to_string('frr/policy.frr.j2', policy) return None + def apply(policy): bgp_daemon = 'bgpd' zebra_daemon = 'zebra' @@ -203,7 +286,8 @@ def apply(policy): frr_cfg.modify_section(r'^bgp community-list .*') frr_cfg.modify_section(r'^bgp extcommunity-list .*') frr_cfg.modify_section(r'^bgp large-community-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) frr_cfg.commit_configuration(bgp_daemon) @@ -214,13 +298,15 @@ def apply(policy): frr_cfg.modify_section(r'^ipv6 access-list .*') frr_cfg.modify_section(r'^ip prefix-list .*') frr_cfg.modify_section(r'^ipv6 prefix-list .*') - frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(r'^route-map .*', stop_pattern='^exit', + remove_stop_mark=True) if 'new_frr_config' in policy: frr_cfg.add_before(frr.default_add_before, policy['new_frr_config']) frr_cfg.commit_configuration(zebra_daemon) return None + if __name__ == '__main__': try: c = get_config() 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/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py index b247ce2ab..d28ced4fd 100755 --- a/src/conf_mode/protocols_nhrp.py +++ b/src/conf_mode/protocols_nhrp.py @@ -14,10 +14,10 @@ # 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 vyos.config import Config from vyos.configdict import node_changed -from vyos.firewall import find_nftables_rule -from vyos.firewall import remove_nftables_rule from vyos.template import render from vyos.util import process_named_running from vyos.util import run @@ -26,6 +26,7 @@ from vyos import airbag airbag.enable() opennhrp_conf = '/run/opennhrp/opennhrp.conf' +nhrp_nftables_conf = '/run/nftables_nhrp.conf' def get_config(config=None): if config: @@ -84,28 +85,23 @@ def verify(nhrp): return None def generate(nhrp): + if not os.path.exists(nhrp_nftables_conf): + nhrp['first_install'] = True + render(opennhrp_conf, 'nhrp/opennhrp.conf.j2', nhrp) + render(nhrp_nftables_conf, 'nhrp/nftables.conf.j2', nhrp) return None def apply(nhrp): - if 'tunnel' in nhrp: - for tunnel, tunnel_conf in nhrp['tunnel'].items(): - if 'source_address' in nhrp['if_tunnel'][tunnel]: - comment = f'VYOS_NHRP_{tunnel}' - source_address = nhrp['if_tunnel'][tunnel]['source_address'] - - rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', ['ip protocol gre', f'ip saddr {source_address}', 'ip daddr 224.0.0.0/4']) - if not rule_handle: - run(f'sudo nft insert rule ip filter VYOS_FW_OUTPUT ip protocol gre ip saddr {source_address} ip daddr 224.0.0.0/4 counter drop comment "{comment}"') - - for tunnel in nhrp['del_tunnels']: - comment = f'VYOS_NHRP_{tunnel}' - rule_handle = find_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', [f'comment "{comment}"']) - if rule_handle: - remove_nftables_rule('ip filter', 'VYOS_FW_OUTPUT', rule_handle) + nft_rc = run(f'nft -f {nhrp_nftables_conf}') + if nft_rc != 0: + raise ConfigError('Failed to apply NHRP tunnel firewall rules') action = 'restart' if nhrp and 'tunnel' in nhrp else 'stop' - run(f'systemctl {action} opennhrp.service') + service_rc = run(f'systemctl {action} opennhrp.service') + if service_rc != 0: + raise ConfigError(f'Failed to {action} the NHRP service') + return None if __name__ == '__main__': diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 5b4874ba2..0582d32be 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -198,6 +198,58 @@ def verify(ospf): if 'master' not in tmp or tmp['master'] != vrf: raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!') + # Segment routing checks + if dict_search('segment_routing.global_block', ospf): + g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf) + g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf) + + # If segment routing global block high or low value is blank, throw error + if not (g_low_label_value or g_high_label_value): + raise ConfigError('Segment routing global-block requires both low and high value!') + + # If segment routing global block low value is higher than the high value, throw error + if int(g_low_label_value) > int(g_high_label_value): + raise ConfigError('Segment routing global-block low value must be lower than high value') + + if dict_search('segment_routing.local_block', ospf): + if dict_search('segment_routing.global_block', ospf) == None: + raise ConfigError('Segment routing local-block requires global-block to be configured!') + + l_high_label_value = dict_search('segment_routing.local_block.high_label_value', ospf) + l_low_label_value = dict_search('segment_routing.local_block.low_label_value', ospf) + + # If segment routing local-block high or low value is blank, throw error + if not (l_low_label_value or l_high_label_value): + raise ConfigError('Segment routing local-block requires both high and low value!') + + # If segment routing local-block low value is higher than the high value, throw error + if int(l_low_label_value) > int(l_high_label_value): + raise ConfigError('Segment routing local-block low value must be lower than high value') + + # local-block most live outside global block + global_range = range(int(g_low_label_value), int(g_high_label_value) +1) + local_range = range(int(l_low_label_value), int(l_high_label_value) +1) + + # Check for overlapping ranges + 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', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if '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', ospf): + for prefix, prefix_config in ospf['segment_routing']['prefix'].items(): + if '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 def generate(ospf): diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index a2e411e49..ee4fe42ab 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -61,6 +61,7 @@ def verify(proxy): if not proxy: return None + aliases = [] processes = process_iter(['name', 'cmdline']) if 'device' in proxy: for device, device_config in proxy['device'].items(): @@ -75,6 +76,12 @@ def verify(proxy): if 'ssh' in device_config and 'port' not in device_config['ssh']: raise ConfigError(f'Port "{device}" requires SSH port to be set!') + if 'alias' in device_config: + if device_config['alias'] in aliases: + raise ConfigError("Console aliases must be unique") + else: + aliases.append(device_config['alias']) + return None def generate(proxy): 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_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 53df006a4..427cb6911 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -42,7 +42,7 @@ systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' def get_nft_filter_chains(): """ Get nft chains for table filter """ - nft = cmd('nft --json list table ip filter') + nft = cmd('nft --json list table ip vyos_filter') nft = json.loads(nft) chain_list = [] 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/ssh.py b/src/conf_mode/ssh.py index 2bbd7142a..8746cc701 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -73,6 +73,9 @@ def verify(ssh): if not ssh: return None + if 'rekey' in ssh and 'data' not in ssh['rekey']: + raise ConfigError(f'Rekey data is required!') + verify_vrf(ssh) return None diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index fc2723ece..bd9cc3b89 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -40,6 +40,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +autologout_file = "/etc/profile.d/autologout.sh" radius_config_file = "/etc/pam_radius_auth.conf" def get_local_users(): @@ -203,6 +204,13 @@ def generate(login): if os.path.isfile(radius_config_file): os.unlink(radius_config_file) + if 'timeout' in login: + render(autologout_file, 'login/autologout.j2', login, + permission=0o755, user='root', group='root') + else: + if os.path.isfile(autologout_file): + os.unlink(autologout_file) + return None 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..77a425f8b 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 @@ -264,7 +265,7 @@ def verify(ipsec): ike = ra_conf['ike_group'] if dict_search(f'ike_group.{ike}.key_exchange', ipsec) != 'ikev2': - raise ConfigError('IPSec remote-access connections requires IKEv2!') + raise ConfigError('IPsec remote-access connections requires IKEv2!') else: raise ConfigError(f"Missing ike-group on {name} remote-access config") @@ -307,10 +308,10 @@ def verify(ipsec): for pool in ra_conf['pool']: if pool == 'dhcp': if dict_search('remote_access.dhcp.server', ipsec) == None: - raise ConfigError('IPSec DHCP server is not configured!') + raise ConfigError('IPsec DHCP server is not configured!') elif pool == 'radius': if dict_search('remote_access.radius.server', ipsec) == None: - raise ConfigError('IPSec RADIUS server is not configured!') + raise ConfigError('IPsec RADIUS server is not configured!') if dict_search('authentication.client_mode', ra_conf) != 'eap-radius': raise ConfigError('RADIUS IP pool requires eap-radius client authentication!') @@ -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/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py deleted file mode 100755 index a52c52706..000000000 --- a/src/conf_mode/zone_policy.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -# -# 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 -# 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 - -from json import loads -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run -from vyos.xml import defaults -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -nftables_conf = '/run/nftables_zone.conf' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['zone-policy'] - zone_policy = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - zone_policy['firewall'] = conf.get_config_dict(['firewall'], - key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) - - if 'zone' in zone_policy: - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base + ['zone']) - for zone in zone_policy['zone']: - zone_policy['zone'][zone] = dict_merge(default_values, - zone_policy['zone'][zone]) - - return zone_policy - -def verify(zone_policy): - # bail out early - looks like removal from running config - if not zone_policy: - return None - - local_zone = False - interfaces = [] - - if 'zone' in zone_policy: - for zone, zone_conf in zone_policy['zone'].items(): - if 'local_zone' not in zone_conf and 'interface' not in zone_conf: - raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') - - if 'local_zone' in zone_conf: - if local_zone: - raise ConfigError('There cannot be multiple local zones') - if 'interface' in zone_conf: - raise ConfigError('Local zone cannot have interfaces assigned') - if 'intra_zone_filtering' in zone_conf: - raise ConfigError('Local zone cannot use intra-zone-filtering') - local_zone = True - - if 'interface' in zone_conf: - found_duplicates = [intf for intf in zone_conf['interface'] if intf in interfaces] - - if found_duplicates: - raise ConfigError(f'Interfaces cannot be assigned to multiple zones') - - interfaces += zone_conf['interface'] - - if 'intra_zone_filtering' in zone_conf: - intra_zone = zone_conf['intra_zone_filtering'] - - if len(intra_zone) > 1: - raise ConfigError('Only one intra-zone-filtering action must be specified') - - if 'firewall' in intra_zone: - v4_name = dict_search_args(intra_zone, 'firewall', 'name') - if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name): - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6-name') - if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6-name', v6_name): - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - - if not v4_name and not v6_name: - raise ConfigError('No firewall names specified for intra-zone-filtering') - - if 'from' in zone_conf: - for from_zone, from_conf in zone_conf['from'].items(): - if from_zone not in zone_policy['zone']: - raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') - - v4_name = dict_search_args(from_conf, 'firewall', 'name') - if v4_name: - if 'name' not in zone_policy['firewall']: - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - if not dict_search_args(zone_policy, 'firewall', 'name', v4_name): - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - v6_name = dict_search_args(from_conf, 'firewall', 'v6_name') - if v6_name: - if 'ipv6_name' not in zone_policy['firewall']: - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - - if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name): - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - - return None - -def has_ipv4_fw(zone_conf): - if 'from' not in zone_conf: - return False - zone_from = zone_conf['from'] - return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'name')]) - -def has_ipv6_fw(zone_conf): - if 'from' not in zone_conf: - return False - zone_from = zone_conf['from'] - return any([True for fz in zone_from if dict_search_args(zone_from, fz, 'firewall', 'ipv6_name')]) - -def get_local_from(zone_policy, local_zone_name): - # Get all zone firewall names from the local zone - out = {} - for zone, zone_conf in zone_policy['zone'].items(): - if zone == local_zone_name: - continue - if 'from' not in zone_conf: - continue - if local_zone_name in zone_conf['from']: - out[zone] = zone_conf['from'][local_zone_name] - return out - -def cleanup_commands(): - commands = [] - for table in ['ip filter', 'ip6 filter']: - json_str = cmd(f'nft -t -j list table {table}') - obj = loads(json_str) - if 'nftables' not in obj: - continue - for item in obj['nftables']: - if 'rule' in item: - chain = item['rule']['chain'] - handle = item['rule']['handle'] - if 'expr' not in item['rule']: - continue - for expr in item['rule']['expr']: - target = dict_search_args(expr, 'jump', 'target') - if not target: - continue - if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"): - commands.append(f'delete rule {table} {chain} handle {handle}') - for item in obj['nftables']: - if 'chain' in item: - if item['chain']['name'].startswith("VZONE"): - chain = item['chain']['name'] - commands.append(f'delete chain {table} {chain}') - return commands - -def generate(zone_policy): - data = zone_policy or {} - - if os.path.exists(nftables_conf): # Check to see if we've run before - data['cleanup_commands'] = cleanup_commands() - - if 'zone' in data: - for zone, zone_conf in data['zone'].items(): - zone_conf['ipv4'] = has_ipv4_fw(zone_conf) - zone_conf['ipv6'] = has_ipv6_fw(zone_conf) - - if 'local_zone' in zone_conf: - zone_conf['from_local'] = get_local_from(data, zone) - - render(nftables_conf, 'zone_policy/nftables.j2', data) - return None - -def apply(zone_policy): - install_result = run(f'nft -f {nftables_conf}') - if install_result != 0: - raise ConfigError('Failed to apply zone-policy') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index 4feb7e09a..411429510 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -109,3 +109,7 @@ net.ipv4.neigh.default.gc_thresh3 = 8192 net.ipv6.neigh.default.gc_thresh1 = 1024 net.ipv6.neigh.default.gc_thresh2 = 4096 net.ipv6.neigh.default.gc_thresh3 = 8192 + +# Enable global RFS (Receive Flow Steering) configuration. RFS is inactive +# until explicitly configured at the interface level +net.core.rps_sock_flow_entries = 32768 diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py index bf4bfd05d..cbc2bfe6b 100755 --- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -11,7 +11,7 @@ def get_nft_filter_chains(): """ Get list of nft chains for table filter """ - nft = cmd('/usr/sbin/nft --json list table ip filter') + nft = cmd('/usr/sbin/nft --json list table ip vyos_filter') nft = json.loads(nft) chain_list = [] @@ -27,7 +27,7 @@ def get_nftables_details(name): """ Get dict, counters packets and bytes for chain """ - command = f'/usr/sbin/nft list chain ip filter {name}' + command = f'/usr/sbin/nft list chain ip vyos_filter {name}' try: results = cmd(command) except: @@ -60,7 +60,7 @@ def get_nft_telegraf(name): Get data for telegraf in influxDB format """ for rule, rule_config in get_nftables_details(name).items(): - print(f'nftables,table=filter,chain={name},' + print(f'nftables,table=vyos_filter,chain={name},' f'ruleid={rule} ' f'pkts={rule_config["packets"]}i,' f'bytes={rule_config["bytes"]}i ' diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 new file mode 100755 index 000000000..ce527acf5 --- /dev/null +++ b/src/migration-scripts/firewall/7-to-8 @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T2199: Migrate interface firewall nodes to firewall interfaces <ifname> <direction> name/ipv6-name <name> +# T2199: Migrate zone-policy to firewall node + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['firewall'] +zone_base = ['zone-policy'] +config = ConfigTree(config_file) + +if not config.exists(base) and not config.exists(zone_base): + # Nothing to do + exit(0) + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): + if_path = ['interfaces', iftype, ifname] + ifname_full = ifname + + if vif: + if_path += ['vif', vif] + ifname_full = f'{ifname}.{vif}' + elif vifs: + if_path += ['vif-s', vifs] + ifname_full = f'{ifname}.{vifs}' + if vifc: + if_path += ['vif-c', vifc] + ifname_full = f'{ifname}.{vifs}.{vifc}' + + if not config.exists(if_path + ['firewall']): + return + + if not config.exists(['firewall', 'interface']): + config.set(['firewall', 'interface']) + config.set_tag(['firewall', 'interface']) + + config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full]) + config.delete(if_path + ['firewall']) + +for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + +if config.exists(zone_base + ['zone']): + config.set(['firewall', 'zone']) + config.set_tag(['firewall', 'zone']) + + for zone in config.list_nodes(zone_base + ['zone']): + config.copy(zone_base + ['zone', zone], ['firewall', 'zone', zone]) + config.delete(zone_base) + +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/ids/0-to-1 b/src/migration-scripts/ids/0-to-1 new file mode 100755 index 000000000..9f08f7dc7 --- /dev/null +++ b/src/migration-scripts/ids/0-to-1 @@ -0,0 +1,56 @@ +#!/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 sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +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 = ['service', 'ids', 'ddos-protection'] +config = ConfigTree(config_file) + +if not config.exists(base + ['threshold']): + # Nothing to do + exit(0) +else: + if config.exists(base + ['threshold', 'fps']): + tmp = config.return_value(base + ['threshold', 'fps']) + config.delete(base + ['threshold', 'fps']) + config.set(base + ['threshold', 'general', 'fps'], value=tmp) + if config.exists(base + ['threshold', 'mbps']): + tmp = config.return_value(base + ['threshold', 'mbps']) + config.delete(base + ['threshold', 'mbps']) + config.set(base + ['threshold', 'general', 'mbps'], value=tmp) + if config.exists(base + ['threshold', 'pps']): + tmp = config.return_value(base + ['threshold', 'pps']) + config.delete(base + ['threshold', 'pps']) + config.set(base + ['threshold', 'general', 'pps'], value=tmp) + +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/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/isis/1-to-2 b/src/migration-scripts/isis/1-to-2 new file mode 100755 index 000000000..f914ea995 --- /dev/null +++ b/src/migration-scripts/isis/1-to-2 @@ -0,0 +1,46 @@ +#!/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/>. + +# T4739 refactor, and remove "on" from segment routing from the configuration + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +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) + +# Check if ISIS segment routing is configured. Then check if segment routing "on" exists, then delete the "on" as it is no longer needed. This is for global configuration. +if config.exists(['protocols', 'isis']): + if config.exists(['protocols', 'isis', 'segment-routing']): + if config.exists(['protocols', 'isis', 'segment-routing', 'enable']): + config.delete(['protocols', 'isis', 'segment-routing', 'enable']) + +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/policy/3-to-4 b/src/migration-scripts/policy/3-to-4 new file mode 100755 index 000000000..bae30cffc --- /dev/null +++ b/src/migration-scripts/policy/3-to-4 @@ -0,0 +1,162 @@ +#!/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/>. + +# T4660: change cli +# from: set policy route-map FOO rule 10 set community 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set community replace <community> +# Multiple value +# to: set policy route-map FOO rule 10 set community add <community> +# to: set policy route-map FOO rule 10 set community none +# +# from: set policy route-map FOO rule 10 set large-community 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set large-community replace <community> +# Multiple value +# to: set policy route-map FOO rule 10 set large-community add <community> +# to: set policy route-map FOO rule 10 set large-community none +# +# from: set policy route-map FOO rule 10 set extecommunity [rt|soo] 'TEXT' +# Multiple value +# to: set policy route-map FOO rule 10 set extcommunity [rt|soo] <community> + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + + +# Migration function for large and regular communities +def community_migrate(config: ConfigTree, rule: list[str]) -> bool: + """ + + :param config: configuration object + :type config: ConfigTree + :param rule: Path to variable + :type rule: list[str] + :return: True if additive presents in community string + :rtype: bool + """ + community_list = list((config.return_value(rule)).split(" ")) + config.delete(rule) + if 'none' in community_list: + config.set(rule + ['none']) + return False + else: + community_action: str = 'replace' + if 'additive' in community_list: + community_action = 'add' + community_list.remove('additive') + for community in community_list: + config.set(rule + [community_action], value=community, + replace=False) + if community_action == 'replace': + return False + else: + return True + + +# Migration function for extcommunities +def extcommunity_migrate(config: ConfigTree, rule: list[str]) -> None: + """ + + :param config: configuration object + :type config: ConfigTree + :param rule: Path to variable + :type rule: list[str] + """ + # if config.exists(rule + ['bandwidth']): + # bandwidth: str = config.return_value(rule + ['bandwidth']) + # config.delete(rule + ['bandwidth']) + # config.set(rule + ['bandwidth'], value=bandwidth) + + if config.exists(rule + ['rt']): + community_list = list((config.return_value(rule + ['rt'])).split(" ")) + config.delete(rule + ['rt']) + for community in community_list: + config.set(rule + ['rt'], value=community, replace=False) + + if config.exists(rule + ['soo']): + community_list = list((config.return_value(rule + ['soo'])).split(" ")) + config.delete(rule + ['soo']) + for community in community_list: + config.set(rule + ['soo'], value=community, replace=False) + + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name: str = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base: list[str] = ['policy', 'route-map'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +for route_map in config.list_nodes(base): + if not config.exists(base + [route_map, 'rule']): + continue + for rule in config.list_nodes(base + [route_map, 'rule']): + base_rule: list[str] = base + [route_map, 'rule', rule, 'set'] + + # IF additive presents in coummunity then comm-list is redundant + isAdditive: bool = True + #### Change Set community ######## + if config.exists(base_rule + ['community']): + isAdditive = community_migrate(config, + base_rule + ['community']) + + #### Change Set community-list delete migrate ######## + if config.exists(base_rule + ['comm-list', 'comm-list']): + if isAdditive: + tmp = config.return_value( + base_rule + ['comm-list', 'comm-list']) + config.delete(base_rule + ['comm-list']) + config.set(base_rule + ['community', 'delete'], value=tmp) + else: + config.delete(base_rule + ['comm-list']) + + isAdditive = False + #### Change Set large-community ######## + if config.exists(base_rule + ['large-community']): + isAdditive = community_migrate(config, + base_rule + ['large-community']) + + #### Change Set large-community delete by List ######## + if config.exists(base_rule + ['large-comm-list-delete']): + if isAdditive: + tmp = config.return_value( + base_rule + ['large-comm-list-delete']) + config.delete(base_rule + ['large-comm-list-delete']) + config.set(base_rule + ['large-community', 'delete'], + value=tmp) + else: + config.delete(base_rule + ['large-comm-list-delete']) + + #### Change Set extcommunity ######## + extcommunity_migrate(config, base_rule + ['extcommunity']) +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/bridge.py b/src/op_mode/bridge.py index fe8dadd70..5a821a287 100755 --- a/src/op_mode/bridge.py +++ b/src/op_mode/bridge.py @@ -22,7 +22,7 @@ import typing from sys import exit from tabulate import tabulate -from vyos.util import cmd +from vyos.util import cmd, rc_cmd from vyos.util import dict_search import vyos.opmode @@ -57,7 +57,11 @@ def _get_raw_data_fdb(bridge): """Get MAC-address for the bridge brX :returns list """ - json_data = cmd(f'sudo bridge --json fdb show br {bridge}') + code, json_data = rc_cmd(f'sudo bridge --json fdb show br {bridge}') + # From iproute2 fdb.c, fdb_show() will only exit(-1) in case of + # non-existent bridge device; raise error. + if code == 255: + raise vyos.opmode.UnconfiguredSubsystem(f"no such bridge device {bridge}") data_dict = json.loads(json_data) return data_dict diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py index b27aa6060..fff537936 100755 --- a/src/op_mode/conntrack.py +++ b/src/op_mode/conntrack.py @@ -48,6 +48,14 @@ def _get_raw_data(family): Return: dictionary """ xml = _get_xml_data(family) + if len(xml) == 0: + output = {'conntrack': + { + 'error': True, + 'reason': 'entries not found' + } + } + return output return _xml_to_dict(xml) @@ -72,7 +80,8 @@ def get_formatted_output(dict_data): :return: formatted output """ data_entries = [] - #dict_data = _get_raw_data(family) + if 'error' in dict_data['conntrack']: + return 'Entries not found' for entry in dict_data['conntrack']['flow']: orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {} reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {} diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 0aea17b3a..950feb625 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -24,43 +24,33 @@ from vyos.config import Config from vyos.util import cmd from vyos.util import dict_search_args -def get_firewall_interfaces(conf, firewall, name=None, ipv6=False): - interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - +def get_firewall_interfaces(firewall, name=None, ipv6=False): directions = ['in', 'out', 'local'] - def parse_if(ifname, if_conf): - if 'firewall' in if_conf: + if 'interface' in firewall: + for ifname, if_conf in firewall['interface'].items(): for direction in directions: - if direction in if_conf['firewall']: - fw_conf = if_conf['firewall'][direction] - name_str = f'({ifname},{direction})' - - if 'name' in fw_conf: - fw_name = fw_conf['name'] + if direction not in if_conf: + continue - if not name: - firewall['name'][fw_name]['interface'].append(name_str) - elif not ipv6 and name == fw_name: - firewall['interface'].append(name_str) + fw_conf = if_conf[direction] + name_str = f'({ifname},{direction})' - if 'ipv6_name' in fw_conf: - fw_name = fw_conf['ipv6_name'] + if 'name' in fw_conf: + fw_name = fw_conf['name'] - if not name: - firewall['ipv6_name'][fw_name]['interface'].append(name_str) - elif ipv6 and name == fw_name: - firewall['interface'].append(name_str) + if not name: + firewall['name'][fw_name]['interface'].append(name_str) + elif not ipv6 and name == fw_name: + firewall['interface'].append(name_str) - for iftype in ['vif', 'vif_s', 'vif_c']: - if iftype in if_conf: - for vifname, vif_conf in if_conf[iftype].items(): - parse_if(f'{ifname}.{vifname}', vif_conf) + if 'ipv6_name' in fw_conf: + fw_name = fw_conf['ipv6_name'] - for iftype, iftype_conf in interfaces.items(): - for ifname, if_conf in iftype_conf.items(): - parse_if(ifname, if_conf) + if not name: + firewall['ipv6_name'][fw_name]['interface'].append(name_str) + elif ipv6 and name == fw_name: + firewall['interface'].append(name_str) return firewall @@ -83,13 +73,13 @@ def get_config_firewall(conf, name=None, ipv6=False, interfaces=True): for fw_name, name_conf in firewall['ipv6_name'].items(): name_conf['interface'] = [] - get_firewall_interfaces(conf, firewall, name, ipv6) + get_firewall_interfaces(firewall, name, ipv6) return firewall def get_nftables_details(name, ipv6=False): suffix = '6' if ipv6 else '' name_prefix = 'NAME6_' if ipv6 else 'NAME_' - command = f'sudo nft list chain ip{suffix} filter {name_prefix}{name}' + command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{name}' try: results = cmd(command) except: diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py index 21561d16f..a22f04c45 100755 --- a/src/op_mode/ikev2_profile_generator.py +++ b/src/op_mode/ikev2_profile_generator.py @@ -119,7 +119,7 @@ config_base = ipsec_base + ['remote-access', 'connection'] pki_base = ['pki'] conf = ConfigTreeQuery() if not conf.exists(config_base): - exit('IPSec remote-access is not configured!') + exit('IPsec remote-access is not configured!') profile_name = 'VyOS IKEv2 Profile' if args.profile: @@ -131,7 +131,7 @@ if args.name: conn_base = config_base + [args.connection] if not conf.exists(conn_base): - exit(f'IPSec remote-access connection "{args.connection}" does not exist!') + exit(f'IPsec remote-access connection "{args.connection}" does not exist!') data = conf.get_config_dict(conn_base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) @@ -178,7 +178,7 @@ for _, proposal in ike_proposal.items(): proposal['hash'] in set(vyos2client_integrity) and proposal['dh_group'] in set(supported_dh_groups)): - # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme + # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] @@ -191,7 +191,7 @@ count = 1 for _, proposal in esp_proposals.items(): if {'encryption', 'hash'} <= set(proposal): if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity): - # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme + # We 're-code' from the VyOS IPsec proposals to the Apple naming scheme proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 1339d5b92..845dbbb2c 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): @@ -109,7 +109,7 @@ def _get_formatted_output_rules(data, direction, family): if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any' for index, match in enumerate(jmespath.search('rule.expr[*].match', rule)): if 'payload' in match['left']: - if 'prefix' in match['right'] or 'set' in match['right']: + if isinstance(match['right'], dict) and ('prefix' in match['right'] or 'set' in match['right']): # Merge dict src/dst l3_l4 parameters my_dict = {**match['left']['payload'], **match['right']} my_dict['op'] = match['op'] @@ -136,10 +136,15 @@ def _get_formatted_output_rules(data, direction, family): dport = my_dict.get('set') dport = ','.join(map(str, dport)) else: - if jmespath.search('left.payload.field', match) == 'saddr': + field = jmespath.search('left.payload.field', match) + if field == 'saddr': saddr = match.get('right') - if jmespath.search('left.payload.field', match) == 'daddr': + elif field == 'daddr': daddr = match.get('right') + elif field == 'sport': + sport = match.get('right') + elif field == 'dport': + dport = match.get('right') else: saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' diff --git a/src/op_mode/route.py b/src/op_mode/route.py index e1eee5bbf..e1eee5bbf 100644..100755 --- a/src/op_mode/route.py +++ b/src/op_mode/route.py 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/storage.py b/src/op_mode/storage.py new file mode 100755 index 000000000..75964c493 --- /dev/null +++ b/src/op_mode/storage.py @@ -0,0 +1,60 @@ +#!/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 sys + +import vyos.opmode +from vyos.util import cmd + + +def _get_system_storage(only_persistent=False): + if not only_persistent: + cmd_str = 'df -h -x squashf' + else: + cmd_str = 'df -h -t ext4 --output=source,size,used,avail,pcent' + + res = cmd(cmd_str) + + return res + +def _get_raw_data(): + out = _get_system_storage(only_persistent=True) + lines = out.splitlines() + lists = [l.split() for l in lines] + res = {lists[0][i]: lists[1][i] for i in range(len(lists[0]))} + + return res + +def _get_formatted_output(): + return _get_system_storage() + +def show(raw: bool): + if raw: + return _get_raw_data() + + return _get_formatted_output() + + +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/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/op_mode/show_uptime.py b/src/op_mode/uptime.py index b70c60cf8..2ebe6783b 100755 --- a/src/op_mode/show_uptime.py +++ b/src/op_mode/uptime.py @@ -14,7 +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/>. -def get_uptime_seconds(): +import sys + +import vyos.opmode + +def _get_uptime_seconds(): from re import search from vyos.util import read_file @@ -23,7 +27,7 @@ def get_uptime_seconds(): return int(float(seconds)) -def get_load_averages(): +def _get_load_averages(): from re import search from vyos.util import cmd from vyos.cpu import get_core_count @@ -40,19 +44,17 @@ def get_load_averages(): return res -def get_raw_data(): +def _get_raw_data(): from vyos.util import seconds_to_human res = {} - res["uptime_seconds"] = get_uptime_seconds() - res["uptime"] = seconds_to_human(get_uptime_seconds()) - res["load_average"] = get_load_averages() + res["uptime_seconds"] = _get_uptime_seconds() + res["uptime"] = seconds_to_human(_get_uptime_seconds()) + res["load_average"] = _get_load_averages() return res -def get_formatted_output(): - data = get_raw_data() - +def _get_formatted_output(data): out = "Uptime: {}\n\n".format(data["uptime"]) avgs = data["load_average"] out += "Load averages:\n" @@ -62,5 +64,19 @@ def get_formatted_output(): return out +def show(raw: bool): + uptime_data = _get_raw_data() + + if raw: + return uptime_data + else: + return _get_formatted_output(uptime_data) + if __name__ == '__main__': - print(get_formatted_output()) + 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/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py index 00f34564a..4b44c5c15 100755 --- a/src/op_mode/vpn_ike_sa.py +++ b/src/op_mode/vpn_ike_sa.py @@ -71,7 +71,7 @@ if __name__ == '__main__': args = parser.parse_args() if not process_named_running('charon'): - print("IPSec Process NOT Running") + print("IPsec Process NOT Running") sys.exit(0) ike_sa(args.peer, args.nat) diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index d8ceefae6..d75d72582 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -31,54 +31,21 @@ class VyosDirective(SchemaDirectiveVisitor): field.resolve = func return field - -class ConfigureDirective(VyosDirective): - """ - Class providing implementation of 'configure' directive in schema. - """ - def visit_field_definition(self, field, object_type): - super().visit_field_definition(field, object_type, - make_resolver=make_configure_resolver) - -class ShowConfigDirective(VyosDirective): - """ - Class providing implementation of 'show' directive in schema. - """ - def visit_field_definition(self, field, object_type): - super().visit_field_definition(field, object_type, - make_resolver=make_show_config_resolver) - -class SystemStatusDirective(VyosDirective): - """ - Class providing implementation of 'system_status' directive in schema. - """ - def visit_field_definition(self, field, object_type): - super().visit_field_definition(field, object_type, - make_resolver=make_system_status_resolver) - -class ConfigFileDirective(VyosDirective): - """ - Class providing implementation of 'configfile' directive in schema. - """ - def visit_field_definition(self, field, object_type): - super().visit_field_definition(field, object_type, - make_resolver=make_config_file_resolver) - -class ShowDirective(VyosDirective): +class ConfigSessionQueryDirective(VyosDirective): """ - Class providing implementation of 'show' directive in schema. + Class providing implementation of 'configsessionquery' directive in schema. """ def visit_field_definition(self, field, object_type): super().visit_field_definition(field, object_type, - make_resolver=make_show_resolver) + make_resolver=make_config_session_query_resolver) -class ImageDirective(VyosDirective): +class ConfigSessionMutationDirective(VyosDirective): """ - Class providing implementation of 'image' directive in schema. + Class providing implementation of 'configsessionmutation' directive in schema. """ def visit_field_definition(self, field, object_type): super().visit_field_definition(field, object_type, - make_resolver=make_image_resolver) + make_resolver=make_config_session_mutation_resolver) class GenOpQueryDirective(VyosDirective): """ @@ -96,11 +63,16 @@ class GenOpMutationDirective(VyosDirective): super().visit_field_definition(field, object_type, make_resolver=make_gen_op_mutation_resolver) -directives_dict = {"configure": ConfigureDirective, - "showconfig": ShowConfigDirective, - "systemstatus": SystemStatusDirective, - "configfile": ConfigFileDirective, - "show": ShowDirective, - "image": ImageDirective, +class SystemStatusDirective(VyosDirective): + """ + Class providing implementation of 'system_status' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_system_status_resolver) + +directives_dict = {"configsessionquery": ConfigSessionQueryDirective, + "configsessionmutation": ConfigSessionMutationDirective, "genopquery": GenOpQueryDirective, - "genopmutation": GenOpMutationDirective} + "genopmutation": GenOpMutationDirective, + "systemstatus": SystemStatusDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 1b77cff87..f7d285a77 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -90,11 +90,12 @@ def make_mutation_resolver(mutation_name, class_name, session_func): } except OpModeError as e: typename = type(e).__name__ + msg = str(e) return { "success": False, "errore": ['op_mode_error'], "op_mode_error": {"name": f"{typename}", - "message": op_mode_err_msg.get(typename, "Unknown"), + "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"), "vyos_code": op_mode_err_code.get(typename, 9999)} } except Exception as error: @@ -105,24 +106,9 @@ def make_mutation_resolver(mutation_name, class_name, session_func): return func_impl -def make_prefix_resolver(mutation_name, prefix=[]): - for pre in prefix: - Pre = pre.capitalize() - if Pre in mutation_name: - class_name = mutation_name.replace(Pre, '', 1) - return make_mutation_resolver(mutation_name, class_name, pre) - raise Exception - -def make_configure_resolver(mutation_name): - class_name = mutation_name - return make_mutation_resolver(mutation_name, class_name, 'configure') - -def make_config_file_resolver(mutation_name): - return make_prefix_resolver(mutation_name, prefix=['save', 'load']) - -def make_image_resolver(mutation_name): - return make_prefix_resolver(mutation_name, prefix=['add', 'delete']) +def make_config_session_mutation_resolver(mutation_name): + return make_mutation_resolver(mutation_name, mutation_name, + convert_camel_case_to_snake(mutation_name)) def make_gen_op_mutation_resolver(mutation_name): - class_name = mutation_name - return make_mutation_resolver(mutation_name, class_name, 'gen_op_mutation') + return make_mutation_resolver(mutation_name, mutation_name, 'gen_op_mutation') diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 8ae61b704..5f3a7d005 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -90,11 +90,12 @@ def make_query_resolver(query_name, class_name, session_func): } except OpModeError as e: typename = type(e).__name__ + msg = str(e) return { "success": False, "errors": ['op_mode_error'], "op_mode_error": {"name": f"{typename}", - "message": op_mode_err_msg.get(typename, "Unknown"), + "message": msg if msg else op_mode_err_msg.get(typename, "Unknown"), "vyos_code": op_mode_err_code.get(typename, 9999)} } except Exception as error: @@ -105,18 +106,12 @@ def make_query_resolver(query_name, class_name, session_func): return func_impl -def make_show_config_resolver(query_name): - class_name = query_name - return make_query_resolver(query_name, class_name, 'show_config') - -def make_system_status_resolver(query_name): - class_name = query_name - return make_query_resolver(query_name, class_name, 'system_status') - -def make_show_resolver(query_name): - class_name = query_name - return make_query_resolver(query_name, class_name, 'show') +def make_config_session_query_resolver(query_name): + return make_query_resolver(query_name, query_name, + convert_camel_case_to_snake(query_name)) def make_gen_op_query_resolver(query_name): - class_name = query_name - return make_query_resolver(query_name, class_name, 'gen_op_query') + return make_query_resolver(query_name, query_name, 'gen_op_query') + +def make_system_status_resolver(query_name): + return make_query_resolver(query_name, query_name, 'system_status') diff --git a/src/services/api/graphql/graphql/schema/config_file.graphql b/src/services/api/graphql/graphql/schema/config_file.graphql deleted file mode 100644 index a7263114b..000000000 --- a/src/services/api/graphql/graphql/schema/config_file.graphql +++ /dev/null @@ -1,29 +0,0 @@ -input SaveConfigFileInput { - key: String! - fileName: String -} - -type SaveConfigFile { - fileName: String -} - -type SaveConfigFileResult { - data: SaveConfigFile - success: Boolean! - errors: [String] -} - -input LoadConfigFileInput { - key: String! - fileName: String! -} - -type LoadConfigFile { - fileName: String! -} - -type LoadConfigFileResult { - data: LoadConfigFile - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/configsession.graphql b/src/services/api/graphql/graphql/schema/configsession.graphql new file mode 100644 index 000000000..b1deac4b3 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/configsession.graphql @@ -0,0 +1,115 @@ + +input ShowConfigInput { + key: String! + path: [String!]! + configFormat: String = null +} + +type ShowConfig { + result: Generic +} + +type ShowConfigResult { + data: ShowConfig + success: Boolean! + errors: [String] +} + +extend type Query { + ShowConfig(data: ShowConfigInput) : ShowConfigResult @configsessionquery +} + +input ShowInput { + key: String! + path: [String!]! +} + +type Show { + result: Generic +} + +type ShowResult { + data: Show + success: Boolean! + errors: [String] +} + +extend type Query { + Show(data: ShowInput) : ShowResult @configsessionquery +} + +input SaveConfigFileInput { + key: String! + fileName: String = null +} + +type SaveConfigFile { + result: Generic +} + +type SaveConfigFileResult { + data: SaveConfigFile + success: Boolean! + errors: [String] +} + +extend type Mutation { + SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configsessionmutation +} + +input LoadConfigFileInput { + key: String! + fileName: String! +} + +type LoadConfigFile { + result: Generic +} + +type LoadConfigFileResult { + data: LoadConfigFile + success: Boolean! + errors: [String] +} + +extend type Mutation { + LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configsessionmutation +} + +input AddSystemImageInput { + key: String! + location: String! +} + +type AddSystemImage { + result: Generic +} + +type AddSystemImageResult { + data: AddSystemImage + success: Boolean! + errors: [String] +} + +extend type Mutation { + AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @configsessionmutation +} + +input DeleteSystemImageInput { + key: String! + name: String! +} + +type DeleteSystemImage { + result: Generic +} + +type DeleteSystemImageResult { + data: DeleteSystemImage + success: Boolean! + errors: [String] +} + +extend type Mutation { + DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @configsessionmutation +}
\ No newline at end of file diff --git a/src/services/api/graphql/graphql/schema/dhcp_server.graphql b/src/services/api/graphql/graphql/schema/dhcp_server.graphql deleted file mode 100644 index 345c349ac..000000000 --- a/src/services/api/graphql/graphql/schema/dhcp_server.graphql +++ /dev/null @@ -1,36 +0,0 @@ -input DhcpServerConfigInput { - key: String! - sharedNetworkName: String - subnet: String - defaultRouter: String - nameServer: String - domainName: String - lease: Int - range: Int - start: String - stop: String - dnsForwardingAllowFrom: String - dnsForwardingCacheSize: Int - dnsForwardingListenAddress: String -} - -type DhcpServerConfig { - sharedNetworkName: String - subnet: String - defaultRouter: String - nameServer: String - domainName: String - lease: Int - range: Int - start: String - stop: String - dnsForwardingAllowFrom: String - dnsForwardingCacheSize: Int - dnsForwardingListenAddress: String -} - -type CreateDhcpServerResult { - data: DhcpServerConfig - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql deleted file mode 100644 index 9454d2997..000000000 --- a/src/services/api/graphql/graphql/schema/firewall_group.graphql +++ /dev/null @@ -1,101 +0,0 @@ -input CreateFirewallAddressGroupInput { - key: String! - name: String! - address: [String] -} - -type CreateFirewallAddressGroup { - name: String! - address: [String] -} - -type CreateFirewallAddressGroupResult { - data: CreateFirewallAddressGroup - success: Boolean! - errors: [String] -} - -input UpdateFirewallAddressGroupMembersInput { - key: String! - name: String! - address: [String!]! -} - -type UpdateFirewallAddressGroupMembers { - name: String! - address: [String!]! -} - -type UpdateFirewallAddressGroupMembersResult { - data: UpdateFirewallAddressGroupMembers - success: Boolean! - errors: [String] -} - -input RemoveFirewallAddressGroupMembersInput { - key: String! - name: String! - address: [String!]! -} - -type RemoveFirewallAddressGroupMembers { - name: String! - address: [String!]! -} - -type RemoveFirewallAddressGroupMembersResult { - data: RemoveFirewallAddressGroupMembers - success: Boolean! - errors: [String] -} - -input CreateFirewallAddressIpv6GroupInput { - key: String! - name: String! - address: [String] -} - -type CreateFirewallAddressIpv6Group { - name: String! - address: [String] -} - -type CreateFirewallAddressIpv6GroupResult { - data: CreateFirewallAddressIpv6Group - success: Boolean! - errors: [String] -} - -input UpdateFirewallAddressIpv6GroupMembersInput { - key: String! - name: String! - address: [String!]! -} - -type UpdateFirewallAddressIpv6GroupMembers { - name: String! - address: [String!]! -} - -type UpdateFirewallAddressIpv6GroupMembersResult { - data: UpdateFirewallAddressIpv6GroupMembers - success: Boolean! - errors: [String] -} - -input RemoveFirewallAddressIpv6GroupMembersInput { - key: String! - name: String! - address: [String!]! -} - -type RemoveFirewallAddressIpv6GroupMembers { - name: String! - address: [String!]! -} - -type RemoveFirewallAddressIpv6GroupMembersResult { - data: RemoveFirewallAddressIpv6GroupMembers - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/image.graphql b/src/services/api/graphql/graphql/schema/image.graphql deleted file mode 100644 index 485033875..000000000 --- a/src/services/api/graphql/graphql/schema/image.graphql +++ /dev/null @@ -1,31 +0,0 @@ -input AddSystemImageInput { - key: String! - location: String! -} - -type AddSystemImage { - location: String - result: String -} - -type AddSystemImageResult { - data: AddSystemImage - success: Boolean! - errors: [String] -} - -input DeleteSystemImageInput { - key: String! - name: String! -} - -type DeleteSystemImage { - name: String - result: String -} - -type DeleteSystemImageResult { - data: DeleteSystemImage - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql b/src/services/api/graphql/graphql/schema/interface_ethernet.graphql deleted file mode 100644 index 8a17d919f..000000000 --- a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql +++ /dev/null @@ -1,19 +0,0 @@ -input InterfaceEthernetConfigInput { - key: String! - interface: String - address: String - replace: Boolean = true - description: String -} - -type InterfaceEthernetConfig { - interface: String - address: String - description: String -} - -type CreateInterfaceEthernetResult { - data: InterfaceEthernetConfig - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index 624be2620..2acecade4 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -3,34 +3,16 @@ schema { mutation: Mutation } -directive @configure on FIELD_DEFINITION -directive @configfile on FIELD_DEFINITION -directive @show on FIELD_DEFINITION -directive @showconfig on FIELD_DEFINITION directive @systemstatus on FIELD_DEFINITION -directive @image on FIELD_DEFINITION +directive @configsessionquery on FIELD_DEFINITION +directive @configsessionmutation on FIELD_DEFINITION directive @genopquery on FIELD_DEFINITION directive @genopmutation on FIELD_DEFINITION scalar Generic type Query { - Show(data: ShowInput) : ShowResult @show - ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig SystemStatus(data: SystemStatusInput) : SystemStatusResult @systemstatus } -type Mutation { - CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure - CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure - CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure - UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure - RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure - CreateFirewallAddressIpv6Group(data: CreateFirewallAddressIpv6GroupInput) : CreateFirewallAddressIpv6GroupResult @configure - UpdateFirewallAddressIpv6GroupMembers(data: UpdateFirewallAddressIpv6GroupMembersInput) : UpdateFirewallAddressIpv6GroupMembersResult @configure - RemoveFirewallAddressIpv6GroupMembers(data: RemoveFirewallAddressIpv6GroupMembersInput) : RemoveFirewallAddressIpv6GroupMembersResult @configure - SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile - LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile - AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image - DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image -} +type Mutation diff --git a/src/services/api/graphql/graphql/schema/show.graphql b/src/services/api/graphql/graphql/schema/show.graphql deleted file mode 100644 index 278ed536b..000000000 --- a/src/services/api/graphql/graphql/schema/show.graphql +++ /dev/null @@ -1,15 +0,0 @@ -input ShowInput { - key: String! - path: [String!]! -} - -type Show { - path: [String] - result: String -} - -type ShowResult { - data: Show - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/graphql/schema/show_config.graphql b/src/services/api/graphql/graphql/schema/show_config.graphql deleted file mode 100644 index 5a1fe43da..000000000 --- a/src/services/api/graphql/graphql/schema/show_config.graphql +++ /dev/null @@ -1,21 +0,0 @@ -""" -Use 'scalar Generic' for show config output, to avoid attempts to -JSON-serialize in case of JSON output. -""" - -input ShowConfigInput { - key: String! - path: [String!]! - configFormat: String -} - -type ShowConfig { - path: [String] - result: Generic -} - -type ShowConfigResult { - data: ShowConfig - success: Boolean! - errors: [String] -} diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py index 8dadcc9f3..3c1a3d45b 100755 --- a/src/services/api/graphql/session/composite/system_status.py +++ b/src/services/api/graphql/session/composite/system_status.py @@ -30,8 +30,8 @@ def get_system_version() -> dict: return show_version.show(raw=True, funny=False) def get_system_uptime() -> dict: - show_uptime = load_op_mode_as_module('show_uptime.py') - return show_uptime.get_raw_data() + show_uptime = load_op_mode_as_module('uptime.py') + return show_uptime._get_raw_data() def get_system_ram_usage() -> dict: show_ram = load_op_mode_as_module('memory.py') diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py index 93e1c328e..f990e63d0 100644 --- a/src/services/api/graphql/session/session.py +++ b/src/services/api/graphql/session/session.py @@ -45,40 +45,6 @@ class Session: except Exception: self._op_mode_list = None - def configure(self): - session = self._session - data = self._data - func_base_name = self._name - - tmpl_file = f'{func_base_name}.tmpl' - cmd_file = f'/tmp/{func_base_name}.cmds' - tmpl_dir = directories['api_templates'] - - try: - render(cmd_file, tmpl_file, data, location=tmpl_dir) - commands = [] - with open(cmd_file) as f: - lines = f.readlines() - for line in lines: - commands.append(line.split()) - for cmd in commands: - if cmd[0] == 'set': - session.set(cmd[1:]) - elif cmd[0] == 'delete': - session.delete(cmd[1:]) - else: - raise ValueError('Operation must be "set" or "delete"') - session.commit() - except Exception as error: - raise error - - def delete_path_if_childless(self, path): - session = self._session - config = Config(session.get_session_env()) - if not config.list_nodes(path): - session.delete(path) - session.commit() - def show_config(self): session = self._session data = self._data @@ -87,14 +53,14 @@ class Session: try: out = session.show_config(data['path']) if data.get('config_format', '') == 'json': - config_tree = vyos.configtree.ConfigTree(out) + config_tree = ConfigTree(out) out = json.loads(config_tree.to_json()) except Exception as error: raise error return out - def save(self): + def save_config_file(self): session = self._session data = self._data if 'file_name' not in data or not data['file_name']: @@ -105,7 +71,7 @@ class Session: except Exception as error: raise error - def load(self): + def load_config_file(self): session = self._session data = self._data @@ -127,7 +93,7 @@ class Session: return out - def add(self): + def add_system_image(self): session = self._session data = self._data @@ -138,7 +104,7 @@ class Session: return res - def delete(self): + def delete_system_image(self): session = self._session data = self._data diff --git a/src/services/api/graphql/utils/config_session_function.py b/src/services/api/graphql/utils/config_session_function.py new file mode 100644 index 000000000..fc0dd7a87 --- /dev/null +++ b/src/services/api/graphql/utils/config_session_function.py @@ -0,0 +1,28 @@ +# typing information for native configsession functions; used to generate +# schema definition files +import typing + +def show_config(path: list[str], configFormat: typing.Optional[str]): + pass + +def show(path: list[str]): + pass + +queries = {'show_config': show_config, + 'show': show} + +def save_config_file(fileName: typing.Optional[str]): + pass +def load_config_file(fileName: str): + pass +def add_system_image(location: str): + pass +def delete_system_image(name: str): + pass + +mutations = {'save_config_file': save_config_file, + 'load_config_file': load_config_file, + 'add_system_image': add_system_image, + 'delete_system_image': delete_system_image} + + diff --git a/src/services/api/graphql/utils/schema_from_config_session.py b/src/services/api/graphql/utils/schema_from_config_session.py new file mode 100755 index 000000000..ea78aaf88 --- /dev/null +++ b/src/services/api/graphql/utils/schema_from_config_session.py @@ -0,0 +1,119 @@ +#!/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/>. +# +# +# A utility to generate GraphQL schema defintions from typing information of +# (wrappers of) native configsession functions. + +import os +import json +from inspect import signature, getmembers, isfunction, isclass, getmro +from jinja2 import Template + +if __package__ is None or __package__ == '': + from util import snake_to_pascal_case, map_type_name +else: + from . util import snake_to_pascal_case, map_type_name + +# this will be run locally before the build +SCHEMA_PATH = '../graphql/schema' + +schema_data: dict = {'schema_name': '', + 'schema_fields': []} + +query_template = """ +input {{ schema_name }}Input { + key: String! + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Query { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery +} +""" + +mutation_template = """ +input {{ schema_name }}Input { + key: String! + {%- for field_entry in schema_fields %} + {{ field_entry }} + {%- endfor %} +} + +type {{ schema_name }} { + result: Generic +} + +type {{ schema_name }}Result { + data: {{ schema_name }} + success: Boolean! + errors: [String] +} + +extend type Mutation { + {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation +} +""" + +def create_schema(func_name: str, func: callable, template: str) -> str: + sig = signature(func) + + field_dict = {} + for k in sig.parameters: + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) + + schema_fields = [] + for k,v in field_dict.items(): + schema_fields.append(k+': '+v) + + schema_data['schema_name'] = snake_to_pascal_case(func_name) + schema_data['schema_fields'] = schema_fields + + j2_template = Template(template) + res = j2_template.render(schema_data) + + return res + +def generate_config_session_definitions(): + from config_session_function import queries, mutations + + results = [] + for name,func in queries.items(): + res = create_schema(name, func, query_template) + results.append(res) + + for name,func in mutations.items(): + res = create_schema(name, func, mutation_template) + results.append(res) + + out = '\n'.join(results) + with open(f'{SCHEMA_PATH}/configsession.graphql', 'w') as f: + f.write(out) + +if __name__ == '__main__': + generate_config_session_definitions() diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index 379d15250..57d63628b 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -20,15 +20,16 @@ import os import json -import typing from inspect import signature, getmembers, isfunction, isclass, getmro from jinja2 import Template from vyos.defaults import directories if __package__ is None or __package__ == '': from util import load_as_module, is_op_mode_function_name, is_show_function_name + from util import snake_to_pascal_case, map_type_name else: from . util import load_as_module, is_op_mode_function_name, is_show_function_name + from . util import snake_to_pascal_case, map_type_name OP_MODE_PATH = directories['op_mode'] SCHEMA_PATH = directories['api_schema'] @@ -103,35 +104,12 @@ type {{ name }} implements OpModeError { {%- endfor %} """ -def _snake_to_pascal_case(name: str) -> str: - res = ''.join(map(str.title, name.split('_'))) - return res - -def _map_type_name(type_name: type, optional: bool = False) -> str: - if type_name == str: - return 'String!' if not optional else 'String = null' - if type_name == int: - return 'Int!' if not optional else 'Int = null' - if type_name == bool: - return 'Boolean!' if not optional else 'Boolean = false' - if typing.get_origin(type_name) == list: - if not optional: - return f'[{_map_type_name(typing.get_args(type_name)[0])}]!' - return f'[{_map_type_name(typing.get_args(type_name)[0])}]' - # typing.Optional is typing.Union[_, NoneType] - if (typing.get_origin(type_name) is typing.Union and - typing.get_args(type_name)[1] == type(None)): - return f'{_map_type_name(typing.get_args(type_name)[0], optional=True)}' - - # scalar 'Generic' is defined in schema.graphql - return 'Generic' - def create_schema(func_name: str, base_name: str, func: callable) -> str: sig = signature(func) field_dict = {} for k in sig.parameters: - field_dict[sig.parameters[k].name] = _map_type_name(sig.parameters[k].annotation) + field_dict[sig.parameters[k].name] = map_type_name(sig.parameters[k].annotation) # It is assumed that if one is generating a schema for a 'show_*' # function, that 'get_raw_data' is present and 'raw' is desired. @@ -142,7 +120,7 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str: for k,v in field_dict.items(): schema_fields.append(k+': '+v) - schema_data['schema_name'] = _snake_to_pascal_case(func_name + '_' + base_name) + schema_data['schema_name'] = snake_to_pascal_case(func_name + '_' + base_name) schema_data['schema_fields'] = schema_fields if is_show_function_name(func_name): diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/utils/util.py index 073126853..da2bcdb5b 100644 --- a/src/services/api/graphql/utils/util.py +++ b/src/services/api/graphql/utils/util.py @@ -15,6 +15,7 @@ import os import re +import typing import importlib.util from vyos.defaults import directories @@ -74,3 +75,26 @@ def split_compound_op_mode_name(name: str, files: list): pair = (pair[0], f[0]) return pair return (name, '') + +def snake_to_pascal_case(name: str) -> str: + res = ''.join(map(str.title, name.split('_'))) + return res + +def map_type_name(type_name: type, optional: bool = False) -> str: + if type_name == str: + return 'String!' if not optional else 'String = null' + if type_name == int: + return 'Int!' if not optional else 'Int = null' + if type_name == bool: + return 'Boolean!' if not optional else 'Boolean = false' + if typing.get_origin(type_name) == list: + if not optional: + return f'[{map_type_name(typing.get_args(type_name)[0])}]!' + return f'[{map_type_name(typing.get_args(type_name)[0])}]' + # typing.Optional is typing.Union[_, NoneType] + if (typing.get_origin(type_name) is typing.Union and + typing.get_args(type_name)[1] == type(None)): + return f'{map_type_name(typing.get_args(type_name)[0], optional=True)}' + + # scalar 'Generic' is defined in schema.graphql + return 'Generic' 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/accel-radius-dictionary b/src/validators/accel-radius-dictionary new file mode 100755 index 000000000..05287e770 --- /dev/null +++ b/src/validators/accel-radius-dictionary @@ -0,0 +1,13 @@ +#!/bin/sh + +DICT_PATH=/usr/share/accel-ppp/radius +NAME=$1 + +if [ -n "$NAME" -a -e $DICT_PATH/dictionary.$NAME ]; then + exit 0 +else + echo "$NAME is not a valid RADIUS dictionary name" + echo "Please make sure that $DICT_PATH/dictionary.$NAME file exists" + exit 1 +fi + diff --git a/src/validators/bgp-extended-community b/src/validators/bgp-extended-community new file mode 100755 index 000000000..b69ae3449 --- /dev/null +++ b/src/validators/bgp-extended-community @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +# 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from argparse import ArgumentParser +from sys import exit + +from vyos.template import is_ipv4 + +COMM_MAX_2_OCTET: int = 65535 +COMM_MAX_4_OCTET: int = 4294967295 + +if __name__ == '__main__': + # add an argument with community + parser: ArgumentParser = ArgumentParser() + parser.add_argument('community', type=str) + args = parser.parse_args() + community: str = args.community + if community.count(':') != 1: + print("Invalid community format") + exit(1) + try: + # try to extract community parts from an argument + comm_left: str = community.split(':')[0] + comm_right: int = int(community.split(':')[1]) + + # check if left part is an IPv4 address + if is_ipv4(comm_left) and 0 <= comm_right <= COMM_MAX_2_OCTET: + exit() + # check if a left part is a number + if 0 <= int(comm_left) <= COMM_MAX_2_OCTET \ + and 0 <= comm_right <= COMM_MAX_4_OCTET: + exit() + + except Exception: + # fail if something was wrong + print("Invalid community format") + exit(1) + + # fail if none of validators catched the value + print("Invalid community format") + exit(1)
\ No newline at end of file diff --git a/src/validators/bgp-large-community b/src/validators/bgp-large-community new file mode 100755 index 000000000..386398308 --- /dev/null +++ b/src/validators/bgp-large-community @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +# 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from argparse import ArgumentParser +from sys import exit + +from vyos.template import is_ipv4 + +COMM_MAX_4_OCTET: int = 4294967295 + +if __name__ == '__main__': + # add an argument with community + parser: ArgumentParser = ArgumentParser() + parser.add_argument('community', type=str) + args = parser.parse_args() + community: str = args.community + if community.count(':') != 2: + print("Invalid community format") + exit(1) + try: + # try to extract community parts from an argument + comm_part1: int = int(community.split(':')[0]) + comm_part2: int = int(community.split(':')[1]) + comm_part3: int = int(community.split(':')[2]) + + # check compatibilities of left and right parts + if 0 <= comm_part1 <= COMM_MAX_4_OCTET \ + and 0 <= comm_part2 <= COMM_MAX_4_OCTET \ + and 0 <= comm_part3 <= COMM_MAX_4_OCTET: + exit(0) + + except Exception: + # fail if something was wrong + print("Invalid community format") + exit(1) + + # fail if none of validators catched the value + print("Invalid community format") + exit(1)
\ No newline at end of file diff --git a/src/validators/bgp-regular-community b/src/validators/bgp-regular-community new file mode 100755 index 000000000..d43a71eae --- /dev/null +++ b/src/validators/bgp-regular-community @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from argparse import ArgumentParser +from sys import exit + +from vyos.template import is_ipv4 + +COMM_MAX_2_OCTET: int = 65535 + +if __name__ == '__main__': + # add an argument with community + parser: ArgumentParser = ArgumentParser() + parser.add_argument('community', type=str) + args = parser.parse_args() + community: str = args.community + if community.count(':') != 1: + print("Invalid community format") + exit(1) + try: + # try to extract community parts from an argument + comm_left: int = int(community.split(':')[0]) + comm_right: int = int(community.split(':')[1]) + + # check compatibilities of left and right parts + if 0 <= comm_left <= COMM_MAX_2_OCTET \ + and 0 <= comm_right <= COMM_MAX_2_OCTET: + exit(0) + except Exception: + # fail if something was wrong + print("Invalid community format") + exit(1) + + # fail if none of validators catched the value + print("Invalid community format") + exit(1)
\ No newline at end of file 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) |