diff options
142 files changed, 1843 insertions, 1168 deletions
@@ -77,7 +77,18 @@ vyxdp: $(MAKE) -C $(XDP_DIR) .PHONY: all -all: clean interface_definitions op_mode_definitions test j2lint vyshim +all: clean interface_definitions op_mode_definitions check test j2lint vyshim + +.PHONY: check +.ONESHELL: +check: + @echo "Checking which CLI scripts are not enabled to work with vyos-configd..." + @for file in `ls src/conf_mode -I__pycache__` + do + if ! grep -q $$file data/configd-include.json; then + echo "* $$file" + fi + done .PHONY: clean clean: diff --git a/data/configd-include.json b/data/configd-include.json index b77d48001..2c3c389d6 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1,9 +1,12 @@ [ +"arp.py", "bcast_relay.py", "conntrack.py", "conntrack_sync.py", "dhcp_relay.py", +"dhcp_server.py", "dhcpv6_relay.py", +"dhcpv6_server.py", "dns_forwarding.py", "dynamic_dns.py", "flow_accounting_conf.py", @@ -24,6 +27,7 @@ "interfaces-pppoe.py", "interfaces-pseudo-ethernet.py", "interfaces-tunnel.py", +"interfaces-vti.py", "interfaces-vxlan.py", "interfaces-wireguard.py", "interfaces-wireless.py", diff --git a/data/templates/dhcp-client/daemon-options.j2 b/data/templates/dhcp-client/daemon-options.j2 new file mode 100644 index 000000000..b21ad08ab --- /dev/null +++ b/data/templates/dhcp-client/daemon-options.j2 @@ -0,0 +1,4 @@ +### Autogenerated by interface.py ### +{% set if_metric = '-e IF_METRIC=' ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined else '' %} +DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ ifname }}.conf -pf /var/lib/dhcp/dhclient_{{ ifname }}.pid -lf /var/lib/dhcp/dhclient_{{ ifname }}.leases {{ if_metric }} {{ ifname }}" + diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl deleted file mode 100644 index 5b3bff73f..000000000 --- a/data/templates/dhcp-client/daemon-options.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -### Autogenerated by interface.py ### - -DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ ifname }}.conf -pf /var/lib/dhcp/dhclient_{{ ifname }}.pid -lf /var/lib/dhcp/dhclient_{{ ifname }}.leases{{" -e IF_METRIC=" ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined }} {{ ifname }}" - diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.j2 index 83fb93dc1..cc5ddf09c 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.j2 @@ -8,12 +8,12 @@ initial-interval 2; interface "{{ ifname }}" { send host-name "{{ dhcp_options.host_name }}"; {% if dhcp_options.client_id is vyos_defined %} -{% set client_id = dhcp_options.client_id %} +{% set client_id = dhcp_options.client_id %} {# Use HEX representation of client-id as it is send in MAC-address style using hex characters. If not HEX, use double quotes ASCII format #} -{% if not dhcp_options.client_id.split(':') | length >= 5 %} -{% set client_id = '"' + dhcp_options.client_id + '"' %} -{% endif %} - send dhcp-client-identifier {{ client_id }}; +{% if not dhcp_options.client_id.split(':') | length >= 5 %} +{% set client_id = '"' + dhcp_options.client_id + '"' %} +{% endif %} + send dhcp-client-identifier {{ client_id }}; {% endif %} {% if dhcp_options.vendor_class_id is vyos_defined %} send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}"; diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.j2 index 085cfe5a9..e136b1789 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.j2 @@ -8,53 +8,53 @@ interface {{ ifname }} { {% if address is vyos_defined and 'dhcpv6' in address %} request domain-name-servers; request domain-name; -{% if dhcpv6_options.parameters_only is vyos_defined %} +{% if dhcpv6_options.parameters_only is vyos_defined %} information-only; -{% endif %} -{% if dhcpv6_options.temporary is not vyos_defined %} +{% endif %} +{% if dhcpv6_options.temporary is not vyos_defined %} send ia-na 0; # non-temporary address -{% endif %} -{% if dhcpv6_options.rapid_commit is vyos_defined %} +{% endif %} +{% if dhcpv6_options.rapid_commit is vyos_defined %} send rapid-commit; # wait for immediate reply instead of advertisements -{% endif %} +{% endif %} {% endif %} {% if dhcpv6_options.pd is vyos_defined %} -{% for pd in dhcpv6_options.pd %} +{% for pd in dhcpv6_options.pd %} send ia-pd {{ pd }}; # prefix delegation #{{ pd }} -{% endfor %} +{% endfor %} {% endif %} }; {% if address is vyos_defined and 'dhcpv6' in address %} -{% if dhcpv6_options.temporary is not vyos_defined %} +{% if dhcpv6_options.temporary is not vyos_defined %} id-assoc na 0 { # Identity association for non temporary address }; -{% endif %} +{% endif %} {% endif %} {% if dhcpv6_options.pd is vyos_defined %} -{% for pd, pd_config in dhcpv6_options.pd.items() %} +{% for pd, pd_config in dhcpv6_options.pd.items() %} id-assoc pd {{ pd }} { {# length got a default value #} 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 %} +{% 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 %} 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 %} }; -{% endfor %} +{% endfor %} {% endif %} diff --git a/data/templates/dhcp-relay/dhcrelay.conf.tmpl b/data/templates/dhcp-relay/dhcrelay.conf.j2 index 11710bd8e..11710bd8e 100644 --- a/data/templates/dhcp-relay/dhcrelay.conf.tmpl +++ b/data/templates/dhcp-relay/dhcrelay.conf.j2 diff --git a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl b/data/templates/dhcp-relay/dhcrelay6.conf.j2 index 1fd5de18c..6365346b4 100644 --- a/data/templates/dhcp-relay/dhcrelay6.conf.tmpl +++ b/data/templates/dhcp-relay/dhcrelay6.conf.j2 @@ -3,18 +3,18 @@ {# upstream_interface is mandatory so it's always present #} {% set upstream = namespace(value='') %} {% for interface, config in upstream_interface.items() %} -{% for address in config.address %} -{% set upstream.value = upstream.value ~ '-u ' ~ address ~ '%' ~ interface ~ ' ' %} -{% endfor %} +{% for address in config.address %} +{% set upstream.value = upstream.value ~ '-u ' ~ address ~ '%' ~ interface ~ ' ' %} +{% endfor %} {% endfor %} {# listen_interface is mandatory so it's always present #} {% set listen = namespace(value='') %} {% for interface, config in listen_interface.items() %} -{% if config.address is vyos_defined %} -{% set listen.value = listen.value ~ '-l ' ~ config.address ~ '%' ~ interface ~ ' ' %} -{% else %} -{% set listen.value = listen.value ~ '-l ' ~ interface ~ ' ' %} -{% endif %} +{% if config.address is vyos_defined %} +{% set listen.value = listen.value ~ '-l ' ~ config.address ~ '%' ~ interface ~ ' ' %} +{% else %} +{% set listen.value = listen.value ~ '-l ' ~ interface ~ ' ' %} +{% endif %} {% endfor %} OPTIONS="{{ listen.value }} {{ upstream.value }} -c {{ max_hop_count }} {{ '-I' if use_interface_id_option is vyos_defined }}" diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.j2 index efc144a1e..4c2da0aa5 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.j2 @@ -23,24 +23,33 @@ option rfc3442-static-route code 121 = array of integer 8; option windows-static-route code 249 = array of integer 8; option wpad-url code 252 = text; +# Vendor specific options - Ubiquiti Networks +option space ubnt; +option ubnt.unifi-controller code 1 = ip-address; +class "ubnt" { + match if substring (option vendor-class-identifier , 0, 4) = "ubnt"; + option vendor-class-identifier "ubnt"; + vendor-option-space ubnt; +} + {% if global_parameters is vyos_defined %} # The following {{ global_parameters | length }} line(s) have been added as # global-parameters in the CLI and have not been validated !!! -{% for parameter in global_parameters %} +{% for parameter in global_parameters %} {{ parameter }} -{% endfor %} +{% endfor %} {% endif %} {% if failover is vyos_defined %} # DHCP failover configuration failover peer "{{ failover.name }}" { -{% if failover.status == 'primary' %} +{% if failover.status == 'primary' %} primary; mclt 1800; split 128; -{% elif failover.status == 'secondary' %} +{% elif failover.status == 'secondary' %} secondary; -{% endif %} +{% endif %} address {{ failover.source_address }}; port 647; peer address {{ failover.remote }}; @@ -53,170 +62,173 @@ failover peer "{{ failover.name }}" { {% if listen_address is vyos_defined %} # DHCP server serving relay subnet, we need a connector to the real world -{% for address in listen_address %} +{% for address in listen_address %} # Connected subnet statement for listen-address {{ address }} subnet {{ address | network_from_ipv4 }} netmask {{ address | netmask_from_ipv4 }} { } -{% endfor %} +{% endfor %} {% endif %} # Shared network configration(s) {% if shared_network_name is vyos_defined %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} +{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} shared-network {{ network }} { -{% if network_config.authoritative is vyos_defined %} +{% if network_config.authoritative is vyos_defined %} authoritative; -{% endif %} -{% if network_config.name_server is vyos_defined %} +{% endif %} +{% if network_config.name_server is vyos_defined %} option domain-name-servers {{ network_config.name_server | join(', ') }}; -{% endif %} -{% if network_config.domain_name is vyos_defined %} +{% endif %} +{% if network_config.domain_name is vyos_defined %} option domain-name "{{ network_config.domain_name }}"; -{% endif %} -{% if network_config.domain_search is vyos_defined %} +{% endif %} +{% if network_config.domain_search is vyos_defined %} option domain-search "{{ network_config.domain_search | join('", "') }}"; -{% endif %} -{% if network_config.ntp_server is vyos_defined %} +{% endif %} +{% if network_config.ntp_server is vyos_defined %} option ntp-servers {{ network_config.ntp_server | join(', ') }}; -{% endif %} -{% if network_config.ping_check is vyos_defined %} +{% endif %} +{% if network_config.ping_check is vyos_defined %} ping-check true; -{% endif %} -{% if network_config.shared_network_parameters is vyos_defined %} +{% endif %} +{% if network_config.shared_network_parameters is vyos_defined %} # The following {{ network_config.shared_network_parameters | length }} line(s) # were added as shared-network-parameters in the CLI and have not been validated -{% for parameter in network_config.shared_network_parameters %} +{% for parameter in network_config.shared_network_parameters %} {{ parameter }} -{% endfor %} -{% endif %} -{% if network_config.subnet is vyos_defined %} -{% for subnet, subnet_config in network_config.subnet.items() %} -{% if subnet_config.description is vyos_defined %} - # {{ subnet_config.description }} +{% endfor %} {% endif %} +{% if network_config.subnet is vyos_defined %} +{% for subnet, subnet_config in network_config.subnet.items() %} +{% if subnet_config.description is vyos_defined %} + # {{ subnet_config.description }} +{% endif %} subnet {{ subnet | address_from_cidr }} netmask {{ subnet | netmask_from_cidr }} { -{% if subnet_config.name_server is vyos_defined %} +{% if subnet_config.name_server is vyos_defined %} option domain-name-servers {{ subnet_config.name_server | join(', ') }}; -{% endif %} -{% if subnet_config.domain_name is vyos_defined %} +{% endif %} +{% if subnet_config.domain_name is vyos_defined %} option domain-name "{{ subnet_config.domain_name }}"; -{% endif %} -{% if subnet_config.domain_search is vyos_defined %} +{% endif %} +{% if subnet_config.domain_search is vyos_defined %} option domain-search "{{ subnet_config.domain_search | join('", "') }}"; -{% endif %} -{% if subnet_config.ntp_server is vyos_defined %} +{% endif %} +{% if subnet_config.ntp_server is vyos_defined %} option ntp-servers {{ subnet_config.ntp_server | join(', ') }}; -{% endif %} -{% if subnet_config.pop_server is vyos_defined %} +{% endif %} +{% if subnet_config.pop_server is vyos_defined %} option pop-server {{ subnet_config.pop_server | join(', ') }}; -{% endif %} -{% if subnet_config.smtp_server is vyos_defined %} +{% endif %} +{% if subnet_config.smtp_server is vyos_defined %} option smtp-server {{ subnet_config.smtp_server | join(', ') }}; -{% endif %} -{% if subnet_config.time_server is vyos_defined %} +{% endif %} +{% if subnet_config.time_server is vyos_defined %} option time-servers {{ subnet_config.time_server | join(', ') }}; -{% endif %} -{% if subnet_config.wins_server is vyos_defined %} +{% endif %} +{% if subnet_config.wins_server is vyos_defined %} option netbios-name-servers {{ subnet_config.wins_server | join(', ') }}; -{% endif %} -{% if subnet_config.static_route is vyos_defined %} -{% set static_default_route = '' %} -{% if subnet_config.default_router is vyos_defined %} -{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} -{% endif %} -{% if subnet_config.static_route is vyos_defined %} -{% set rfc3442_routes = [] %} -{% for route, route_options in subnet_config.static_route.items() %} -{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %} -{% endfor %} +{% endif %} +{% if subnet_config.static_route is vyos_defined %} +{% set static_default_route = '' %} +{% if subnet_config.default_router is vyos_defined %} +{% set static_default_route = ', ' ~ '0.0.0.0/0' | isc_static_route(subnet_config.default_router) %} +{% endif %} +{% if subnet_config.static_route is vyos_defined %} +{% set rfc3442_routes = [] %} +{% for route, route_options in subnet_config.static_route.items() %} +{% set rfc3442_routes = rfc3442_routes.append(route | isc_static_route(route_options.next_hop)) %} +{% endfor %} option rfc3442-static-route {{ rfc3442_routes | join(', ') }}{{ static_default_route }}; option windows-static-route {{ rfc3442_routes | join(', ') }}; -{% endif %} -{% endif %} -{% if subnet_config.ip_forwarding is vyos_defined %} +{% endif %} +{% endif %} +{% if subnet_config.ip_forwarding is vyos_defined %} option ip-forwarding true; -{% endif %} -{% if subnet_config.default_router is vyos_defined %} +{% endif %} +{% if subnet_config.default_router is vyos_defined %} option routers {{ subnet_config.default_router }}; -{% endif %} -{% if subnet_config.server_identifier is vyos_defined %} +{% endif %} +{% if subnet_config.server_identifier is vyos_defined %} option dhcp-server-identifier {{ subnet_config.server_identifier }}; -{% endif %} -{% if subnet_config.subnet_parameters is vyos_defined %} +{% endif %} +{% if subnet_config.subnet_parameters is vyos_defined %} # The following {{ subnet_config.subnet_parameters | length }} line(s) were added as # subnet-parameters in the CLI and have not been validated!!! -{% for parameter in subnet_config.subnet_parameters %} +{% for parameter in subnet_config.subnet_parameters %} {{ parameter }} -{% endfor %} -{% endif %} -{% if subnet_config.tftp_server_name is vyos_defined %} +{% endfor %} +{% endif %} +{% if subnet_config.tftp_server_name is vyos_defined %} option tftp-server-name "{{ subnet_config.tftp_server_name }}"; -{% endif %} -{% if subnet_config.bootfile_name is vyos_defined %} +{% endif %} +{% if subnet_config.bootfile_name is vyos_defined %} option bootfile-name "{{ subnet_config.bootfile_name }}"; filename "{{ subnet_config.bootfile_name }}"; -{% endif %} -{% if subnet_config.bootfile_server is vyos_defined %} +{% endif %} +{% if subnet_config.bootfile_server is vyos_defined %} next-server {{ subnet_config.bootfile_server }}; -{% endif %} -{% if subnet_config.bootfile_size is vyos_defined %} +{% endif %} +{% if subnet_config.bootfile_size is vyos_defined %} option boot-size {{ subnet_config.bootfile_size }}; -{% endif %} -{% if subnet_config.time_offset is vyos_defined %} +{% endif %} +{% if subnet_config.time_offset is vyos_defined %} option time-offset {{ subnet_config.time_offset }}; -{% endif %} -{% if subnet_config.wpad_url is vyos_defined %} +{% endif %} +{% if subnet_config.wpad_url is vyos_defined %} option wpad-url "{{ subnet_config.wpad_url }}"; -{% endif %} -{% if subnet_config.client_prefix_length is vyos_defined %} +{% endif %} +{% if subnet_config.client_prefix_length is vyos_defined %} option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }}; -{% endif %} -{% if subnet_config.lease is vyos_defined %} +{% endif %} +{% if subnet_config.lease is vyos_defined %} default-lease-time {{ subnet_config.lease }}; max-lease-time {{ subnet_config.lease }}; -{% endif %} -{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %} +{% endif %} +{% if network_config.ping_check is not vyos_defined and subnet_config.ping_check is vyos_defined %} ping-check true; -{% endif %} -{% if subnet_config.static_mapping is vyos_defined %} -{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} +{% endif %} +{% if subnet_config.static_mapping is vyos_defined %} +{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} host {{ host | replace('_','-') if host_decl_name is vyos_defined else network | replace('_','-') ~ '_' ~ host | replace('_','-') }} { -{% if host_config.ip_address is vyos_defined %} +{% if host_config.ip_address is vyos_defined %} fixed-address {{ host_config.ip_address }}; -{% endif %} +{% endif %} hardware ethernet {{ host_config.mac_address }}; -{% if host_config.static_mapping_parameters is vyos_defined %} +{% if host_config.static_mapping_parameters is vyos_defined %} # The following {{ host_config.static_mapping_parameters | length }} line(s) were added # as static-mapping-parameters in the CLI and have not been validated -{% for parameter in host_config.static_mapping_parameters %} +{% for parameter in host_config.static_mapping_parameters %} {{ parameter }} -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} } -{% endfor %} -{% endif %} -{% if subnet_config.range is vyos_defined %} +{% endfor %} +{% endif %} +{% if subnet_config.vendor_option.ubiquiti.unifi_controller is vyos_defined %} + option ubnt.unifi-controller {{ subnet_config.vendor_option.ubiquiti.unifi_controller }}; +{% endif %} +{% if subnet_config.range is vyos_defined %} {# pool configuration can only be used if there follows a range option #} pool { -{% endif %} -{% if subnet_config.enable_failover is vyos_defined %} +{% endif %} +{% if subnet_config.enable_failover is vyos_defined %} failover peer "{{ failover.name }}"; deny dynamic bootp clients; -{% endif %} -{% if subnet_config.range is vyos_defined %} -{% for range, range_options in subnet_config.range.items() %} +{% endif %} +{% if subnet_config.range is vyos_defined %} +{% for range, range_options in subnet_config.range.items() %} range {{ range_options.start }} {{ range_options.stop }}; -{% endfor %} -{% endif %} -{% if subnet_config.range is vyos_defined %} +{% endfor %} +{% endif %} +{% if subnet_config.range is vyos_defined %} {# pool configuration can only be used if there follows a range option #} } -{% endif %} +{% endif %} } -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} on commit { set shared-networkname = "{{ network }}"; -{% if hostfile_update is vyos_defined %} +{% if hostfile_update is vyos_defined %} set ClientIp = binary-to-ascii(10, 8, ".", leased-address); set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6)); set ClientName = pick-first-value(host-decl-name, option fqdn.hostname, option host-name, "empty_hostname"); @@ -226,9 +238,9 @@ shared-network {{ network }} { } else { log(concat("Hostname is not defined for client with IP: ", ClientIP, " MAC: ", ClientMac)); } -{% endif %} +{% endif %} } } -{% endfor %} +{% endfor %} {% endif %} diff --git a/data/templates/dhcp-server/dhcpdv6.conf.j2 b/data/templates/dhcp-server/dhcpdv6.conf.j2 new file mode 100644 index 000000000..5c3471316 --- /dev/null +++ b/data/templates/dhcp-server/dhcpdv6.conf.j2 @@ -0,0 +1,132 @@ +### Autogenerated by dhcpv6_server.py ### + +# For options please consult the following website: +# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html + +log-facility local7; +{% if preference is vyos_defined %} +option dhcp6.preference {{ preference }}; +{% endif %} + +{% if global_parameters.name_server is vyos_defined %} +option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }}; +{% endif %} + +# Vendor specific options - Cisco +option space cisco code width 2 length width 2; +option cisco.tftp-servers code 1 = array of ip6-address; +option vsio.cisco code 9 = encapsulate cisco; + +# Shared network configration(s) +{% if shared_network_name is vyos_defined %} +{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} +shared-network {{ network }} { +{% if network_config.common_options is vyos_defined %} +{% if network_config.common_options.info_refresh_time is vyos_defined %} + option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }}; +{% endif %} +{% if network_config.common_options.domain_search is vyos_defined %} + option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}"; +{% endif %} +{% if network_config.common_options.name_server is vyos_defined %} + option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }}; +{% endif %} +{% endif %} +{% if network_config.subnet is vyos_defined %} +{% for subnet, subnet_config in network_config.subnet.items() %} + subnet6 {{ subnet }} { +{% if subnet_config.address_range is vyos_defined %} +{% if subnet_config.address_range.prefix is vyos_defined %} +{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %} + range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }}; +{% endfor %} +{% endif %} +{% if subnet_config.address_range.start is vyos_defined %} +{% for address, address_config in subnet_config.address_range.start.items() %} + range6 {{ address }} {{ address_config.stop }}; +{% endfor %} +{% endif %} +{% endif %} +{% if subnet_config.domain_search is vyos_defined %} + option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}"; +{% endif %} +{% if subnet_config.lease_time is vyos_defined %} +{% if subnet_config.lease_time.default is vyos_defined %} + default-lease-time {{ subnet_config.lease_time.default }}; +{% endif %} +{% if subnet_config.lease_time.maximum is vyos_defined %} + max-lease-time {{ subnet_config.lease_time.maximum }}; +{% endif %} +{% if subnet_config.lease_time.minimum is vyos_defined %} + min-lease-time {{ subnet_config.lease_time.minimum }}; +{% endif %} +{% endif %} +{% if subnet_config.name_server is vyos_defined %} + option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }}; +{% endif %} +{% if subnet_config.nis_domain is vyos_defined %} + option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}"; +{% endif %} +{% if subnet_config.nis_server is vyos_defined %} + option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }}; +{% endif %} +{% if subnet_config.nisplus_domain is vyos_defined %} + option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}"; +{% endif %} +{% if subnet_config.nisplus_server is vyos_defined %} + option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }}; +{% endif %} +{% if subnet_config.sip_server is vyos_defined %} +{% set server_ip = [] %} +{% set server_fqdn = [] %} +{% for address in subnet_config.sip_server %} +{% if address | is_ipv6 %} +{% set server_ip = server_ip.append(address) %} +{% else %} +{% set server_fqdn = server_fqdn.append(address) %} +{% endif %} +{% endfor %} +{% if server_ip is vyos_defined and server_ip | length > 0 %} + option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }}; +{% endif %} +{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %} + option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}"; +{% endif %} +{% endif %} +{% if subnet_config.sntp_server is vyos_defined %} + option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }}; +{% endif %} +{% if subnet_config.prefix_delegation.start is vyos_defined %} +{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %} + prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }}; +{% endfor %} +{% endif %} +{% if subnet_config.static_mapping is vyos_defined %} + + # begin configuration of static client mappings +{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} + host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} { +{% if host_config.identifier is vyos_defined %} + host-identifier option dhcp6.client-id {{ host_config.identifier }}; +{% endif %} +{% if host_config.ipv6_address is vyos_defined %} + fixed-address6 {{ host_config.ipv6_address }}; +{% endif %} +{% if host_config.ipv6_prefix is vyos_defined %} + fixed-prefix6 {{ host_config.ipv6_prefix }}; +{% endif %} + } +{% endfor %} +{% endif %} +{% if subnet_config.vendor_option.cisco.tftp_server is vyos_defined %} + option cisco.tftp-servers {{ subnet_config.vendor_option.cisco.tftp_server | join(', ') }}; +{% endif %} + } +{% endfor %} +{% endif %} + on commit { + set shared-networkname = "{{ network }}"; + } +} +{% endfor %} +{% endif %} diff --git a/data/templates/dhcp-server/dhcpdv6.conf.tmpl b/data/templates/dhcp-server/dhcpdv6.conf.tmpl deleted file mode 100644 index 1a55668e1..000000000 --- a/data/templates/dhcp-server/dhcpdv6.conf.tmpl +++ /dev/null @@ -1,124 +0,0 @@ -### Autogenerated by dhcpv6_server.py ### - -# For options please consult the following website: -# https://www.isc.org/wp-content/uploads/2017/08/dhcp43options.html - -log-facility local7; -{% if preference is vyos_defined %} -option dhcp6.preference {{ preference }}; -{% endif %} - -{% if global_parameters.name_server is vyos_defined %} -option dhcp6.name-servers {{ global_parameters.name_server | join(', ') }}; -{% endif %} - -# Shared network configration(s) -{% if shared_network_name is vyos_defined %} -{% for network, network_config in shared_network_name.items() if network_config.disable is not vyos_defined %} -shared-network {{ network }} { -{% if network_config.common_options is vyos_defined %} -{% if network_config.common_options.info_refresh_time is vyos_defined %} - option dhcp6.info-refresh-time {{ network_config.common_options.info_refresh_time }}; -{% endif %} -{% if network_config.common_options.domain_search is vyos_defined %} - option dhcp6.domain-search "{{ network_config.common_options.domain_search | join('", "') }}"; -{% endif %} -{% if network_config.common_options.name_server is vyos_defined %} - option dhcp6.name-servers {{ network_config.common_options.name_server | join(', ') }}; -{% endif %} -{% endif %} -{% if network_config.subnet is vyos_defined %} -{% for subnet, subnet_config in network_config.subnet.items() %} - subnet6 {{ subnet }} { -{% if subnet_config.address_range is vyos_defined %} -{% if subnet_config.address_range.prefix is vyos_defined %} -{% for prefix, prefix_config in subnet_config.address_range.prefix.items() %} - range6 {{ prefix }} {{ "temporary" if prefix_config.temporary is vyos_defined }}; -{% endfor %} -{% endif %} -{% if subnet_config.address_range.start is vyos_defined %} -{% for address, address_config in subnet_config.address_range.start.items() %} - range6 {{ address }} {{ address_config.stop }}; -{% endfor %} -{% endif %} -{% endif %} -{% if subnet_config.domain_search is vyos_defined %} - option dhcp6.domain-search "{{ subnet_config.domain_search | join('", "') }}"; -{% endif %} -{% if subnet_config.lease_time is vyos_defined %} -{% if subnet_config.lease_time.default is vyos_defined %} - default-lease-time {{ subnet_config.lease_time.default }}; -{% endif %} -{% if subnet_config.lease_time.maximum is vyos_defined %} - max-lease-time {{ subnet_config.lease_time.maximum }}; -{% endif %} -{% if subnet_config.lease_time.minimum is vyos_defined %} - min-lease-time {{ subnet_config.lease_time.minimum }}; -{% endif %} -{% endif %} -{% if subnet_config.name_server is vyos_defined %} - option dhcp6.name-servers {{ subnet_config.name_server | join(', ') }}; -{% endif %} -{% if subnet_config.nis_domain is vyos_defined %} - option dhcp6.nis-domain-name "{{ subnet_config.nis_domain }}"; -{% endif %} -{% if subnet_config.nis_server is vyos_defined %} - option dhcp6.nis-servers {{ subnet_config.nis_server | join(', ') }}; -{% endif %} -{% if subnet_config.nisplus_domain is vyos_defined %} - option dhcp6.nisp-domain-name "{{ subnet_config.nisplus_domain }}"; -{% endif %} -{% if subnet_config.nisplus_server is vyos_defined %} - option dhcp6.nisp-servers {{ subnet_config.nisplus_server | join(', ') }}; -{% endif %} -{% if subnet_config.sip_server is vyos_defined %} -{% set server_ip = [] %} -{% set server_fqdn = [] %} -{% for address in subnet_config.sip_server %} -{% if address | is_ipv6 %} -{% set server_ip = server_ip.append(address) %} -{% else %} -{% set server_fqdn = server_fqdn.append(address) %} -{% endif %} -{% endfor %} -{% if server_ip is vyos_defined and server_ip | length > 0 %} - option dhcp6.sip-servers-addresses {{ server_ip | join(', ') }}; -{% endif %} -{% if server_fqdn is vyos_defined and server_fqdn | length > 0 %} - option dhcp6.sip-servers-names "{{ server_fqdn | join('", "') }}"; -{% endif %} -{% endif %} -{% if subnet_config.sntp_server is vyos_defined %} - option dhcp6.sntp-servers {{ subnet_config.sntp_server | join(', ') }}; -{% endif %} -{% if subnet_config.prefix_delegation.start is vyos_defined %} -{% for prefix, prefix_config in subnet_config.prefix_delegation.start.items() %} - prefix6 {{ prefix }} {{ prefix_config.stop }} /{{ prefix_config.prefix_length }}; -{% endfor %} -{% endif %} -{% if subnet_config.static_mapping is vyos_defined %} - - # begin configuration of static client mappings -{% for host, host_config in subnet_config.static_mapping.items() if host_config.disable is not vyos_defined %} - host {{ network | replace('_','-') }}_{{ host | replace('_','-') }} { -{% if host_config.identifier is vyos_defined %} - host-identifier option dhcp6.client-id {{ host_config.identifier }}; -{% endif %} -{% if host_config.ipv6_address is vyos_defined %} - fixed-address6 {{ host_config.ipv6_address }}; -{% endif %} -{% if host_config.ipv6_prefix is vyos_defined %} - fixed-prefix6 {{ host_config.ipv6_prefix }}; -{% endif %} - } -{% endfor %} -{% endif %} - } -{% endfor %} -{% endif %} - on commit { - set shared-networkname = "{{ network }}"; - } -} -{% endfor %} -{% endif %} diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.j2 index 385bef94b..c1950e1bc 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.j2 @@ -1,3 +1,4 @@ +{# j2lint: disable=single-statement-per-line #} ### Autogenerated by dns_forwarding.py ### # XXX: pdns recursor doesn't like whitespace near entry separators, diff --git a/data/templates/dns-forwarding/recursor.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.conf.lua.j2 index e2506238d..e2506238d 100644 --- a/data/templates/dns-forwarding/recursor.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.lua.j2 diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2 index 96cbc35a5..de3269e47 100644 --- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.j2 @@ -1,3 +1,4 @@ +{# j2lint: disable=operator-enclosed-by-spaces #} # Autogenerated by VyOS (vyos-hostsd) # Do not edit, your changes will get overwritten @@ -7,11 +8,11 @@ {# the order of tags, then by the order of nameservers within that tag #} {% set n = namespace(dot_zone_ns='') %} {% for tag in name_server_tags_recursor %} -{% set ns = '' %} -{% if tag in name_servers %} -{% set ns = ns + name_servers[tag]|join(', ') %} -{% set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %} -{% endif %} +{% set ns = '' %} +{% if tag in name_servers %} +{% set ns = ns + name_servers[tag] | join(', ') %} +{% set n.dot_zone_ns = (n.dot_zone_ns, ns) | join(', ') if n.dot_zone_ns != '' else ns %} +{% endif %} # {{ tag }}: {{ ns }} {% endfor %} @@ -21,8 +22,8 @@ {% if forward_zones is vyos_defined %} # zones added via 'service dns forwarding domain' -{% for zone, zonedata in forward_zones.items() %} +{% for zone, zonedata in forward_zones.items() %} {{ "+" if zonedata.recursion_desired is vyos_defined }}{{ zone | replace('_', '-') }}={{ zonedata.server | join(', ') }} -{% endfor %} +{% endfor %} {% endif %} diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.j2 index 04fb72121..987c7de1f 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.j2 @@ -3,28 +3,28 @@ {% if hosts %} -- from 'system static-host-mapping' and DHCP server -{% for tag, taghosts in hosts.items() %} -{% for host, hostprops in taghosts.items() %} +{% for tag, taghosts in hosts.items() %} +{% for host, hostprops in taghosts.items() %} addNTA("{{ host }}.", "{{ tag }}") -{% for a in hostprops['aliases'] %} +{% for a in hostprops['aliases'] %} addNTA("{{ a }}.", "{{ tag }} alias") -{% endfor %} +{% endfor %} +{% endfor %} {% endfor %} -{% endfor %} {% endif %} {% if forward_zones is vyos_defined %} -- from 'service dns forwarding domain' -{% for zone, zonedata in forward_zones.items() %} -{% if zonedata.addnta is vyos_defined %} +{% for zone, zonedata in forward_zones.items() %} +{% if zonedata.addnta is vyos_defined %} addNTA("{{ zone }}", "static") -{% endif %} -{% endfor %} +{% endif %} +{% endfor %} {% endif %} {% if authoritative_zones is vyos_defined %} -- from 'service dns forwarding authoritative-domain' -{% for zone in authoritative_zones %} +{% for zone in authoritative_zones %} addNTA("{{ zone }}", "static") -{% endfor %} +{% endfor %} {% endif %} diff --git a/data/templates/dns-forwarding/recursor.zone.conf.tmpl b/data/templates/dns-forwarding/recursor.zone.conf.j2 index 758871bef..25193c2ec 100644 --- a/data/templates/dns-forwarding/recursor.zone.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.zone.conf.j2 @@ -1,7 +1,6 @@ ; ; Autogenerated by dns_forwarding.py ; -; {% for r in records %} -{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }} +{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }} {% endfor %} diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2 new file mode 100644 index 000000000..3c2d17cbb --- /dev/null +++ b/data/templates/dynamic-dns/ddclient.conf.j2 @@ -0,0 +1,51 @@ +### Autogenerated by dynamic_dns.py ### +daemon=1m +syslog=yes +ssl=yes + +{% if interface is vyos_defined %} +{% for iface, iface_config in interface.items() %} +# ddclient configuration for interface "{{ iface }}" +{% if iface_config.use_web is vyos_defined %} +{% set web_skip = ", web-skip='" ~ iface_config.use_web.skip ~ "'" if iface_config.use_web.skip is vyos_defined else '' %} +use=web, web='{{ iface_config.use_web.url }}'{{ web_skip }} +{% else %} +{{ 'usev6=if' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }} +{% endif %} + +{% if iface_config.rfc2136 is vyos_defined %} +{% for rfc2136, config in iface_config.rfc2136.items() %} +{% for dns_record in config.record if config.record is vyos_defined %} +# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }} +server={{ config.server }} +protocol=nsupdate +password={{ config.key }} +ttl={{ config.ttl }} +zone={{ config.zone }} +{{ dns_record }} + +{% endfor %} +{% endfor %} +{% endif %} + +{% if iface_config.service is vyos_defined %} +{% for service, config in iface_config.service.items() %} +{% for dns_record in config.host_name %} +# DynDNS provider configuration for {{ service }}, {{ dns_record }} +protocol={{ config.protocol }}, +max-interval=28d, +login={{ config.login }}, +password='{{ config.password }}', +{% if config.server is vyos_defined %} +server={{ config.server }}, +{% endif %} +{% if config.zone is vyos_defined %} +zone={{ config.zone }}, +{% endif %} +{{ dns_record }} + +{% endfor %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl deleted file mode 100644 index ee55c9fa6..000000000 --- a/data/templates/dynamic-dns/ddclient.conf.tmpl +++ /dev/null @@ -1,51 +0,0 @@ -### Autogenerated by dynamic_dns.py ### -daemon=1m -syslog=yes -ssl=yes - -{% if interface is vyos_defined %} -{% for iface, iface_config in interface.items() %} -# ddclient configuration for interface "{{ iface }}" -{% if iface_config.use_web is vyos_defined %} -{% set web_skip = ", web-skip='" ~ iface_config.use_web.skip ~ "'" if iface_config.use_web.skip is vyos_defined else '' %} -use=web, web='{{ iface_config.use_web.url }}'{{ web_skip }} -{% else %} -{{ 'usev6=if' if iface_config.ipv6_enable is vyos_defined else 'use=if' }}, if={{ iface }} -{% endif %} - -{% if iface_config.rfc2136 is vyos_defined %} -{% for rfc2136, config in iface_config.rfc2136.items() %} -{% for dns_record in config.record if config.record is vyos_defined %} -# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }} -server={{ config.server }} -protocol=nsupdate -password={{ config.key }} -ttl={{ config.ttl }} -zone={{ config.zone }} -{{ dns_record }} - -{% endfor %} -{% endfor %} -{% endif %} - -{% if iface_config.service is vyos_defined %} -{% for service, config in iface_config.service.items() %} -{% for dns_record in config.host_name %} -# DynDNS provider configuration for {{ service }}, {{ dns_record }} -protocol={{ config.protocol }}, -max-interval=28d, -login={{ config.login }}, -password='{{ config.password }}', -{% if config.server is vyos_defined %} -server={{ config.server }}, -{% endif %} -{% if config.zone is vyos_defined %} -zone={{ config.zone }}, -{% endif %} -{{ dns_record }} - -{% endfor %} -{% endfor %} -{% endif %} -{% endfor %} -{% endif %} diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl index 922f3dcb4..63aa48c77 100644 --- a/data/templates/firewall/nftables-nat.tmpl +++ b/data/templates/firewall/nftables-nat.tmpl @@ -6,14 +6,14 @@ {% 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('!=','') ~ ' }' %} +{% 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('!=','') ~ ' }' %} +{% 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 %} @@ -138,8 +138,9 @@ {% endif %} {% endmacro %} -# Start with clean NAT table -flush table ip nat +# 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' %} @@ -164,6 +165,7 @@ add rule ip raw NAT_CONNTRACK counter accept # # Destination NAT rules build up here # +add rule ip nat PREROUTING 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') }} @@ -172,6 +174,7 @@ add rule ip raw NAT_CONNTRACK counter accept # # Source NAT rules build up here # +add rule ip nat POSTROUTING 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') }} diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index 08b2a3dab..589f03c2c 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -20,10 +20,16 @@ vrf {{ vrf }} {% for interface, interface_config in dhcp.items() %} {% set next_hop = interface | get_dhcp_router %} {% if next_hop is vyos_defined %} -{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.distance }} +{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.dhcp_options.default_route_distance if interface_config.dhcp_options.default_route_distance is vyos_defined }} {% endif %} {% endfor %} {% endif %} +{# IPv4 default routes from PPPoE interfaces #} +{% if pppoe is vyos_defined %} +{% for interface, interface_config in pppoe.items() %} +{{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }} +{% endfor %} +{% endif %} {# IPv6 routing #} {% if route6 is vyos_defined %} {% for prefix, prefix_config in route6.items() %} diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index b21dce9f0..61af85ed4 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -152,7 +152,7 @@ {% endif %} } {% if tunnel_conf.passthrough is vyos_defined %} - peer_{{ name }}_tunnel_{{ tunnel_id }}_passthough { + peer_{{ 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/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl index d3145a500..cf33eec4e 100644 --- a/data/templates/monitoring/telegraf.tmpl +++ b/data/templates/monitoring/telegraf.tmpl @@ -1,12 +1,12 @@ # Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py [agent] - interval = "10s" + interval = "15s" round_interval = true metric_batch_size = 1000 metric_buffer_limit = 10000 - collection_jitter = "0s" - flush_interval = "10s" + collection_jitter = "5s" + flush_interval = "15s" flush_jitter = "0s" precision = "" debug = false diff --git a/data/templates/openvpn/auth.pw.tmpl b/data/templates/openvpn/auth.pw.j2 index 218121062..218121062 100644 --- a/data/templates/openvpn/auth.pw.tmpl +++ b/data/templates/openvpn/auth.pw.j2 diff --git a/data/templates/openvpn/client.conf.tmpl b/data/templates/openvpn/client.conf.j2 index 98c8b0273..2e327e4d3 100644 --- a/data/templates/openvpn/client.conf.tmpl +++ b/data/templates/openvpn/client.conf.j2 @@ -1,30 +1,30 @@ ### Autogenerated by interfaces-openvpn.py ### -{% if ip %} +{% if ip is vyos_defined %} ifconfig-push {{ ip[0] }} {{ server_subnet[0] | netmask_from_cidr }} {% endif %} {% if push_route is vyos_defined %} -{% for route in push_route %} +{% for route in push_route %} push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}" -{% endfor %} +{% endfor %} {% endif %} {% if subnet is vyos_defined %} -{% for network in subnet %} +{% for network in subnet %} iroute {{ network | address_from_cidr }} {{ network | netmask_from_cidr }} -{% endfor %} +{% endfor %} {% endif %} {# ipv6_remote is only set when IPv6 server is enabled #} -{% if ipv6_remote %} +{% if ipv6_remote is vyos_defined %} # IPv6 -{% if ipv6_ip %} +{% if ipv6_ip is vyos_defined %} ifconfig-ipv6-push {{ ipv6_ip[0] }} {{ ipv6_remote }} -{% endif %} -{% for route6 in ipv6_push_route %} +{% endif %} +{% for route6 in ipv6_push_route %} push "route-ipv6 {{ route6 }}" -{% endfor %} -{% for net6 in ipv6_subnet %} +{% endfor %} +{% for net6 in ipv6_subnet %} iroute-ipv6 {{ net6 }} -{% endfor %} +{% endfor %} {% endif %} {% if disable is vyos_defined %} disable diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.j2 index f26680fa3..6dd4ef88d 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.j2 @@ -10,9 +10,9 @@ verb 3 dev-type {{ device_type }} dev {{ ifname }} persist-key -{% if protocol == 'tcp-active' %} +{% if protocol is vyos_defined('tcp-active') %} proto tcp-client -{% elif protocol == 'tcp-passive' %} +{% elif protocol is vyos_defined('tcp-passive') %} proto tcp-server {% else %} proto udp @@ -30,9 +30,9 @@ lport {{ local_port }} rport {{ remote_port }} {% endif %} {% if remote_host is vyos_defined %} -{% for remote in remote_host %} +{% for remote in remote_host %} remote {{ remote }} -{% endfor %} +{% endfor %} {% endif %} {% if shared_secret_key is vyos_defined %} secret /run/openvpn/{{ ifname }}_shared.key @@ -49,88 +49,88 @@ push "redirect-gateway def1" compress lzo {% endif %} -{% if mode == 'client' %} +{% if mode is vyos_defined('client') %} # # OpenVPN Client mode # client nobind -{% elif mode == 'server' %} +{% elif mode is vyos_defined('server') %} # # OpenVPN Server mode # mode server tls-server -{% if server is vyos_defined %} -{% if server.subnet is vyos_defined %} -{% if server.topology is vyos_defined('point-to-point') %} +{% if server is vyos_defined %} +{% if server.subnet is vyos_defined %} +{% if server.topology is vyos_defined('point-to-point') %} topology p2p -{% elif server.topology is vyos_defined %} +{% elif server.topology is vyos_defined %} topology {{ server.topology }} -{% endif %} -{% for subnet in server.subnet %} -{% if subnet | is_ipv4 %} +{% endif %} +{% for subnet in server.subnet %} +{% if subnet | is_ipv4 %} server {{ subnet | address_from_cidr }} {{ subnet | netmask_from_cidr }} nopool {# First ip address is used as gateway. It's allows to use metrics #} -{% if server.push_route is vyos_defined %} -{% for route, route_config in server.push_route.items() %} -{% if route | is_ipv4 %} -push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }}{% if route_config.metric is vyos_defined %} {{ subnet | first_host_address }} {{ route_config.metric }}{% endif %}" -{% elif route | is_ipv6 %} +{% if server.push_route is vyos_defined %} +{% for route, route_config in server.push_route.items() %} +{% if route | is_ipv4 %} +push "route {{ route | address_from_cidr }} {{ route | netmask_from_cidr }} {{ subnet | first_host_address ~ ' ' ~ route_config.metric if route_config.metric is vyos_defined }}" +{% elif route | is_ipv6 %} push "route-ipv6 {{ route }}" -{% endif %} -{% endfor %} -{% endif %} +{% endif %} +{% endfor %} +{% endif %} {# OpenVPN assigns the first IP address to its local interface so the pool used #} {# in net30 topology - where each client receives a /30 must start from the second subnet #} -{% if server.topology is vyos_defined('net30') %} +{% if server.topology is vyos_defined('net30') %} ifconfig-pool {{ subnet | inc_ip('4') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tap' else '' }} -{% else %} +{% else %} {# OpenVPN assigns the first IP address to its local interface so the pool must #} {# start from the second address and end on the last address #} ifconfig-pool {{ subnet | first_host_address | inc_ip('1') }} {{ subnet | last_host_address | dec_ip('1') }} {{ subnet | netmask_from_cidr if device_type == 'tun' else '' }} -{% endif %} -{% elif subnet | is_ipv6 %} +{% endif %} +{% elif subnet | is_ipv6 %} server-ipv6 {{ subnet }} +{% endif %} +{% endfor %} {% endif %} -{% endfor %} -{% endif %} -{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %} +{% if server.client_ip_pool is vyos_defined and server.client_ip_pool.disable is not vyos_defined %} ifconfig-pool {{ server.client_ip_pool.start }} {{ server.client_ip_pool.stop }}{{ server.client_ip_pool.subnet_mask if server.client_ip_pool.subnet_mask is vyos_defined }} -{% endif %} -{% if server.max_connections is vyos_defined %} +{% endif %} +{% if server.max_connections is vyos_defined %} max-clients {{ server.max_connections }} -{% endif %} -{% if server.client is vyos_defined %} +{% endif %} +{% if server.client is vyos_defined %} client-config-dir /run/openvpn/ccd/{{ ifname }} +{% endif %} {% endif %} -{% endif %} -keepalive {{ keep_alive.interval }} {{ keep_alive.interval|int * keep_alive.failure_count|int }} +keepalive {{ keep_alive.interval }} {{ keep_alive.interval | int * keep_alive.failure_count | int }} management /run/openvpn/openvpn-mgmt-intf unix -{% if server is vyos_defined %} -{% if server.reject_unconfigured_clients is vyos_defined %} +{% if server is vyos_defined %} +{% if server.reject_unconfigured_clients is vyos_defined %} ccd-exclusive -{% endif %} +{% endif %} -{% if server.name_server is vyos_defined %} -{% for nameserver in server.name_server %} -{% if nameserver | is_ipv4 %} +{% if server.name_server is vyos_defined %} +{% for nameserver in server.name_server %} +{% if nameserver | is_ipv4 %} push "dhcp-option DNS {{ nameserver }}" -{% elif nameserver | is_ipv6 %} +{% elif nameserver | is_ipv6 %} push "dhcp-option DNS6 {{ nameserver }}" +{% endif %} +{% endfor %} {% endif %} -{% endfor %} -{% endif %} -{% if server.domain_name is vyos_defined %} +{% if server.domain_name is vyos_defined %} push "dhcp-option DOMAIN {{ server.domain_name }}" +{% endif %} +{% if server.mfa.totp is vyos_defined %} +{% set totp_config = server.mfa.totp %} +plugin "{{ plugin_dir }}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifname }}-otp-secrets otp_slop={{ totp_config.slop }} totp_t0={{ totp_config.drift }} totp_step={{ totp_config.step }} totp_digits={{ totp_config.digits }} password_is_cr={{ '1' if totp_config.challenge == 'enable' else '0' }}" +{% endif %} {% endif %} -{% if server.mfa.totp is vyos_defined %} -{% set totp_config = server.mfa.totp %} -plugin "{{ plugin_dir}}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifname }}-otp-secrets {{ 'otp_slop=' ~ totp_config.slop }} {{ 'totp_t0=' ~ totp_config.drift }} {{ 'totp_step=' ~ totp_config.step }} {{ 'totp_digits=' ~ totp_config.digits }} password_is_cr={{ '1' if totp_config.challenge == 'enable' else '0' }}" -{% endif %} -{% endif %} {% else %} # # OpenVPN site-2-site mode @@ -138,80 +138,80 @@ plugin "{{ plugin_dir}}/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{{ ifn ping {{ keep_alive.interval }} ping-restart {{ keep_alive.failure_count }} -{% if device_type == 'tap' %} -{% if local_address is vyos_defined %} -{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} -{% if laddr_conf.subnet_mask is vyos_defined %} +{% if device_type == 'tap' %} +{% if local_address is vyos_defined %} +{% for laddr, laddr_conf in local_address.items() if laddr | is_ipv4 %} +{% if laddr_conf.subnet_mask is vyos_defined %} ifconfig {{ laddr }} {{ laddr_conf.subnet_mask }} +{% endif %} +{% endfor %} {% endif %} -{% endfor %} -{% endif %} -{% else %} -{% for laddr in local_address if laddr | is_ipv4 %} -{% for raddr in remote_address if raddr | is_ipv4 %} +{% else %} +{% for laddr in local_address if laddr | is_ipv4 %} +{% for raddr in remote_address if raddr | is_ipv4 %} ifconfig {{ laddr }} {{ raddr }} -{% endfor %} -{% endfor %} -{% for laddr in local_address if laddr | is_ipv6 %} -{% for raddr in remote_address if raddr | is_ipv6 %} +{% endfor %} +{% endfor %} +{% for laddr in local_address if laddr | is_ipv6 %} +{% for raddr in remote_address if raddr | is_ipv6 %} ifconfig-ipv6 {{ laddr }} {{ raddr }} -{% endfor %} -{% endfor %} -{% endif %} +{% endfor %} +{% endfor %} +{% endif %} {% endif %} {% if tls is vyos_defined %} # TLS options -{% if tls.ca_certificate is vyos_defined %} +{% if tls.ca_certificate is vyos_defined %} ca /run/openvpn/{{ ifname }}_ca.pem -{% endif %} -{% if tls.certificate is vyos_defined %} +{% endif %} +{% if tls.certificate is vyos_defined %} cert /run/openvpn/{{ ifname }}_cert.pem -{% endif %} -{% if tls.private_key is vyos_defined %} +{% endif %} +{% if tls.private_key is vyos_defined %} key /run/openvpn/{{ ifname }}_cert.key -{% endif %} -{% if tls.crypt_key is vyos_defined %} +{% endif %} +{% if tls.crypt_key is vyos_defined %} tls-crypt /run/openvpn/{{ ifname }}_crypt.key -{% endif %} -{% if tls.crl is vyos_defined %} +{% endif %} +{% if tls.crl is vyos_defined %} crl-verify /run/openvpn/{{ ifname }}_crl.pem -{% endif %} -{% if tls.tls_version_min is vyos_defined %} +{% endif %} +{% if tls.tls_version_min is vyos_defined %} tls-version-min {{ tls.tls_version_min }} -{% endif %} -{% if tls.dh_params is vyos_defined %} +{% endif %} +{% if tls.dh_params is vyos_defined %} dh /run/openvpn/{{ ifname }}_dh.pem -{% elif mode is vyos_defined('server') and tls.private_key is vyos_defined %} +{% elif mode is vyos_defined('server') and tls.private_key is vyos_defined %} dh none -{% endif %} -{% if tls.auth_key is vyos_defined %} -{% if mode == 'client' %} +{% endif %} +{% if tls.auth_key is vyos_defined %} +{% if mode == 'client' %} tls-auth /run/openvpn/{{ ifname }}_auth.key 1 -{% elif mode == 'server' %} +{% elif mode == 'server' %} tls-auth /run/openvpn/{{ ifname }}_auth.key 0 +{% endif %} {% endif %} -{% endif %} -{% if tls.role is vyos_defined('active') %} +{% if tls.role is vyos_defined('active') %} tls-client -{% elif tls.role is vyos_defined('passive') %} +{% elif tls.role is vyos_defined('passive') %} tls-server -{% endif %} +{% endif %} {% endif %} # Encryption options {% if encryption is vyos_defined %} -{% if encryption.cipher is vyos_defined %} +{% if encryption.cipher is vyos_defined %} cipher {{ encryption.cipher | openvpn_cipher }} -{% if encryption.cipher is vyos_defined('bf128') %} +{% if encryption.cipher is vyos_defined('bf128') %} keysize 128 -{% elif encryption.cipher is vyos_defined('bf256') %} +{% elif encryption.cipher is vyos_defined('bf256') %} keysize 256 +{% endif %} {% endif %} -{% endif %} -{% if encryption.ncp_ciphers is vyos_defined %} +{% if encryption.ncp_ciphers is vyos_defined %} data-ciphers {{ encryption.ncp_ciphers | openvpn_ncp_ciphers }} -{% endif %} +{% endif %} {% endif %} {% if hash is vyos_defined %} diff --git a/data/templates/openvpn/service-override.conf.j2 b/data/templates/openvpn/service-override.conf.j2 new file mode 100644 index 000000000..616ba3bfc --- /dev/null +++ b/data/templates/openvpn/service-override.conf.j2 @@ -0,0 +1,21 @@ +{% set options = namespace(value='') %} +{% if openvpn_option is vyos_defined %} +{% for option in openvpn_option %} +{# Remove the '--' prefix from variable if it is presented #} +{% if option.startswith('--') %} +{% set option = option.split('--', maxsplit=1)[1] %} +{% endif %} +{# Workaround to pass '--push' options properly. Previously openvpn accepted this option without values in double-quotes #} +{# But now it stopped doing this, so we need to add them for compatibility #} +{# HOWEVER! This is a raw option and we do not promise that this or any other trick will work for all the cases. #} +{# Using 'openvpn-option' you take all responsibility for compatibility for yourself. #} +{% if option.startswith('push') and not (option.startswith('push "') and option.endswith('"')) %} +{% set option = 'push \"%s\"' | format(option.split('push ', maxsplit=1)[1]) %} +{% endif %} +{% set options.value = options.value ~ ' --' ~ option %} +{% endfor %} +{% endif %} +[Service] +ExecStart= +ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid {{ options.value }} + diff --git a/data/templates/openvpn/service-override.conf.tmpl b/data/templates/openvpn/service-override.conf.tmpl deleted file mode 100644 index cba652223..000000000 --- a/data/templates/openvpn/service-override.conf.tmpl +++ /dev/null @@ -1,20 +0,0 @@ -[Service] -ExecStart= -ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid -{%- if openvpn_option is vyos_defined %} -{% for option in openvpn_option %} -{# Remove the '--' prefix from variable if it is presented #} -{% if option.startswith('--') %} -{% set option = option.split('--', maxsplit=1)[1] %} -{% endif %} -{# Workaround to pass '--push' options properly. Previously openvpn accepted this option without values in double-quotes #} -{# But now it stopped doing this, so we need to add them for compatibility #} -{# HOWEVER! This is a raw option and we do not promise that this or any other trick will work for all the cases. #} -{# Using 'openvpn-option' you take all responsibility for compatibility for yourself. #} -{% if option.startswith('push') and not (option.startswith('push "') and option.endswith('"')) %} -{% set option = 'push \"%s\"'|format(option.split('push ', maxsplit=1)[1]) %} -{% endif %} - --{{ option }} -{%- endfor %} -{% endif %} - diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.j2 index 99749b57a..f4001db64 100644 --- a/data/templates/salt-minion/minion.tmpl +++ b/data/templates/salt-minion/minion.j2 @@ -32,17 +32,17 @@ log_file: /var/log/salt/minion # ['garbage', 'trace', 'debug'] # # Default: 'warning' -log_level: {{ log_level }} +log_level: warning # Set the location of the salt master server, if the master server cannot be # resolved, then the minion will fail to start. master: {% for host in master %} -- {{ host }} + - {{ host | bracketize_ipv6 }} {% endfor %} # The user to run salt -user: {{ user }} +user: minion # The directory to store the pki information in pki_dir: /config/salt/pki/minion @@ -52,10 +52,16 @@ pki_dir: /config/salt/pki/minion # Since salt uses detached ids it is possible to run multiple minions on the # same machine but with different ids, this can be useful for salt compute # clusters. -id: {{ salt_id }} - +id: {{ id }} # The number of minutes between mine updates. mine_interval: {{ interval }} -verify_master_pubkey_sign: {{ verify_master_pubkey_sign }} +{% if source_interface is vyos_defined %} +# The name of the interface to use when establishing the connection to the Master. +source_interface_name: {{ source_interface }} +{% endif %} + +# Enables verification of the master-public-signature returned by the master +# in auth-replies. +verify_master_pubkey_sign: {{ 'True' if master_key is vyos_defined else 'False' }} diff --git a/data/templates/vyos-hostsd/hosts.tmpl b/data/templates/vyos-hostsd/hosts.j2 index bc75d384e..5cad983b4 100644 --- a/data/templates/vyos-hostsd/hosts.tmpl +++ b/data/templates/vyos-hostsd/hosts.j2 @@ -1,3 +1,4 @@ +{# j2lint: disable=single-statement-per-line #} ### Autogenerated by VyOS ### ### Do not edit, your changes will get overwritten ### @@ -14,12 +15,12 @@ ff02::2 ip6-allrouters {% if hosts is vyos_defined %} # From 'system static-host-mapping' and DHCP server -{% for tag, taghosts in hosts.items() %} +{% for tag, taghosts in hosts.items() %} # {{ tag }} -{% for host, hostprops in taghosts.items() if hostprops.address is vyos_defined %} -{% for addr in hostprops.address %} -{{ "%-15s" | format(addr) }} {{ host }} {{ hostprops.aliases|join(' ') if hostprops.aliases is vyos_defined }} -{% endfor %} +{% for host, hostprops in taghosts.items() if hostprops.address is vyos_defined %} +{% for addr in hostprops.address %} +{{ "%-15s" | format(addr) }} {{ host }} {{ hostprops.aliases | join(' ') if hostprops.aliases is vyos_defined }} +{% endfor %} +{% endfor %} {% endfor %} -{% endfor %} {% endif %} diff --git a/data/templates/vyos-hostsd/resolv.conf.tmpl b/data/templates/vyos-hostsd/resolv.conf.j2 index 58a5f9312..5f651f1a1 100644 --- a/data/templates/vyos-hostsd/resolv.conf.tmpl +++ b/data/templates/vyos-hostsd/resolv.conf.j2 @@ -5,12 +5,12 @@ {# the order of tags, then by the order of nameservers within that tag #} {% for tag in name_server_tags_system %} -{% if tag in name_servers %} +{% if tag in name_servers %} # {{ tag }} -{% for ns in name_servers[tag] %} +{% for ns in name_servers[tag] %} nameserver {{ ns }} -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} {% endfor %} {% if domain_name %} @@ -18,8 +18,8 @@ domain {{ domain_name }} {% endif %} {% for tag in name_server_tags_system %} -{% if tag in search_domains %} +{% if tag in search_domains %} # {{ tag }} -search {{ search_domains[tag]|join(' ') }} -{% endif %} +search {{ search_domains[tag] | join(' ') }} +{% endif %} {% endfor %} diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in index 4ea2d471d..2c4c1709d 100644 --- a/interface-definitions/dhcp-server.xml.in +++ b/interface-definitions/dhcp-server.xml.in @@ -374,6 +374,18 @@ <leafNode name="tftp-server-name"> <properties> <help>TFTP server name</help> + <valueHelp> + <format>ipv4</format> + <description>TFTP server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>TFTP server FQDN</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="fqdn"/> + </constraint> </properties> </leafNode> <leafNode name="time-offset"> @@ -402,6 +414,32 @@ <multi/> </properties> </leafNode> + <node name="vendor-option"> + <properties> + <help>Vendor Specific Options</help> + </properties> + <children> + <node name="ubiquiti"> + <properties> + <help>Ubiquiti specific parameters</help> + </properties> + <children> + <leafNode name="unifi-controller"> + <properties> + <help>Address of UniFi controller</help> + <valueHelp> + <format>ipv4</format> + <description>IP address of UniFi controller</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> <leafNode name="wins-server"> <properties> <help>IP address for Windows Internet Name Service (WINS) server</help> diff --git a/interface-definitions/dhcpv6-server.xml.in b/interface-definitions/dhcpv6-server.xml.in index fb96571f5..10335b07e 100644 --- a/interface-definitions/dhcpv6-server.xml.in +++ b/interface-definitions/dhcpv6-server.xml.in @@ -338,6 +338,33 @@ </leafNode> </children> </tagNode> + <node name="vendor-option"> + <properties> + <help>Vendor Specific Options</help> + </properties> + <children> + <node name="cisco"> + <properties> + <help>Cisco specific parameters</help> + </properties> + <children> + <leafNode name="tftp-server"> + <properties> + <help>TFTP server name</help> + <valueHelp> + <format>ipv6</format> + <description>TFTP server IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> </children> </tagNode> </children> diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i index f3fc4444c..a56745380 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -69,7 +69,7 @@ #include <include/bgp/afi-allowas-in.xml.i> <leafNode name="as-override"> <properties> - <help>AS for routes sent to this peer to be the local AS</help> + <help>Override ASN in outbound updates to configured neighbor local-as</help> <valueless/> </properties> </leafNode> diff --git a/interface-definitions/include/interface/default-route-distance.xml.i b/interface-definitions/include/interface/default-route-distance.xml.i new file mode 100644 index 000000000..6eda52c91 --- /dev/null +++ b/interface-definitions/include/interface/default-route-distance.xml.i @@ -0,0 +1,15 @@ +<!-- include start from interface/default-route-distance.xml.i --> +<leafNode name="default-route-distance"> + <properties> + <help>Distance for installed default route</help> + <valueHelp> + <format>u32:1-255</format> + <description>Distance for the default route from DHCP server</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-255"/> + </constraint> + </properties> + <defaultValue>210</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i index 098d02919..914b60503 100644 --- a/interface-definitions/include/interface/dhcp-options.xml.i +++ b/interface-definitions/include/interface/dhcp-options.xml.i @@ -19,25 +19,8 @@ <help>Identify the vendor client type to the DHCP server</help> </properties> </leafNode> - <leafNode name="no-default-route"> - <properties> - <help>Do not request routers from DHCP server</help> - <valueless/> - </properties> - </leafNode> - <leafNode name="default-route-distance"> - <properties> - <help>Distance for the default route from DHCP server</help> - <valueHelp> - <format>u32:1-255</format> - <description>Distance for the default route from DHCP server</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-255"/> - </constraint> - </properties> - <defaultValue>210</defaultValue> - </leafNode> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> <leafNode name="reject"> <properties> <help>IP addresses or subnets from which to reject DHCP leases</help> diff --git a/interface-definitions/include/interface/no-default-route.xml.i b/interface-definitions/include/interface/no-default-route.xml.i new file mode 100644 index 000000000..307fcff1e --- /dev/null +++ b/interface-definitions/include/interface/no-default-route.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/dhcp-options.xml.i --> +<leafNode name="no-default-route"> + <properties> + <help>Do not install default route to system</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-df.xml.i b/interface-definitions/include/interface/parameters-df.xml.i new file mode 100644 index 000000000..82436b5e4 --- /dev/null +++ b/interface-definitions/include/interface/parameters-df.xml.i @@ -0,0 +1,26 @@ +<!-- include start from interface/parameters-df.xml.i --> +<leafNode name="df"> + <properties> + <help>Usage of the DF (don't Fragment) bit in outgoing packets</help> + <completionHelp> + <list>set unset inherit</list> + </completionHelp> + <valueHelp> + <format>set</format> + <description>Always set DF (don't fragment) bit</description> + </valueHelp> + <valueHelp> + <format>unset</format> + <description>Always unset DF (don't fragment) bit</description> + </valueHelp> + <valueHelp> + <format>inherit</format> + <description>Copy from the original IP header</description> + </valueHelp> + <constraint> + <regex>(set|unset|inherit)</regex> + </constraint> + </properties> + <defaultValue>unset</defaultValue> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/interface/parameters-dont-fragment.xml.i b/interface-definitions/include/interface/parameters-dont-fragment.xml.i deleted file mode 100644 index d34f0a97b..000000000 --- a/interface-definitions/include/interface/parameters-dont-fragment.xml.i +++ /dev/null @@ -1,8 +0,0 @@ -<!-- include start from interface/parameters-df.xml.i --> -<leafNode name="dont-fragment"> - <properties> - <help>Specifies the usage of the dont fragment (DF) bit</help> - <valueless/> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i index b97971531..0a209bc3a 100644 --- a/interface-definitions/include/version/interfaces-version.xml.i +++ b/interface-definitions/include/version/interfaces-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/interfaces-version.xml.i --> -<syntaxVersion component='interfaces' version='25'></syntaxVersion> +<syntaxVersion component='interfaces' version='26'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index fa5a78be5..9143ba6be 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -35,7 +35,7 @@ <help>IPv4 specific tunnel parameters</help> </properties> <children> - #include <include/interface/parameters-dont-fragment.xml.i> + #include <include/interface/parameters-df.xml.i> #include <include/interface/parameters-tos.xml.i> #include <include/interface/parameters-ttl.xml.i> </children> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 3a0b7a40c..8cd8f8c86 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -21,31 +21,8 @@ #include <include/interface/dial-on-demand.xml.i> #include <include/interface/interface-firewall.xml.i> #include <include/interface/interface-policy.xml.i> - <leafNode name="default-route"> - <properties> - <help>Default route insertion behaviour</help> - <completionHelp> - <list>auto none force</list> - </completionHelp> - <constraint> - <regex>^(auto|none|force)$</regex> - </constraint> - <constraintErrorMessage>PPPoE default-route option must be 'auto', 'none', or 'force'</constraintErrorMessage> - <valueHelp> - <format>auto</format> - <description>Automatically install a default route</description> - </valueHelp> - <valueHelp> - <format>none</format> - <description>Do not install a default route</description> - </valueHelp> - <valueHelp> - <format>force</format> - <description>Replace existing default route</description> - </valueHelp> - </properties> - <defaultValue>auto</defaultValue> - </leafNode> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> #include <include/interface/dhcpv6-options.xml.i> #include <include/interface/description.xml.i> #include <include/interface/disable.xml.i> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 9747b1816..8b50fe1b7 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -69,7 +69,7 @@ <help>IPv4 specific tunnel parameters</help> </properties> <children> - #include <include/interface/parameters-dont-fragment.xml.i> + #include <include/interface/parameters-df.xml.i> #include <include/interface/parameters-tos.xml.i> #include <include/interface/parameters-ttl.xml.i> <leafNode name="ttl"> diff --git a/interface-definitions/salt-minion.xml.in b/interface-definitions/salt-minion.xml.in index d3b022d12..c3219cff3 100644 --- a/interface-definitions/salt-minion.xml.in +++ b/interface-definitions/salt-minion.xml.in @@ -15,20 +15,25 @@ <list>md5 sha1 sha224 sha256 sha384 sha512</list> </completionHelp> <constraint> - <regex>^(md5|sha1|sha224|sha256|sha384|sha512)$</regex> + <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex> </constraint> </properties> + <defaultValue>sha256</defaultValue> </leafNode> <leafNode name="master"> <properties> - <help>The hostname or IP address of the master.</help> + <help>Hostname or IP address of the Salt master server</help> <valueHelp> <format>ipv4</format> - <description>Remote syslog server IPv4 address</description> + <description>Salt server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Salt server IPv6 address</description> </valueHelp> <valueHelp> <format>hostname</format> - <description>Remote syslog server FQDN</description> + <description>Salt server FQDN address</description> </valueHelp> <constraint> <validator name="ip-address"/> @@ -54,12 +59,14 @@ <validator name="numeric" argument="--range 1-1440"/> </constraint> </properties> + <defaultValue>60</defaultValue> </leafNode> <leafNode name="master-key"> <properties> <help>URL with signature of master for auth reply verification</help> </properties> </leafNode> + #include <include/source-interface.xml.i> </children> </node> </children> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index cbdf76fc3..6f82ce611 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -6,13 +6,96 @@ <properties> <help>Monitor last lines of messages file</help> </properties> - <command>tail --follow=name /var/log/messages</command> + <command>journalctl --no-hostname --follow --boot</command> <children> <node name="colored"> <properties> <help>Output log in a colored fashion</help> </properties> - <command>grc tail --follow=name /var/log/messages</command> + <command>grc journalctl --no-hostname --follow --boot</command> + </node> + <node name="dhcp"> + <properties> + <help>Show log for Dynamic Host Control Protocol (DHCP)</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Show log for DHCP server</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCP client logs</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhclient@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCP client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhclient@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <node name="dhcpv6"> + <properties> + <help>Show log for Dynamic Host Control Protocol IPv6 (DHCPv6)</help> + </properties> + <children> + <node name="server"> + <properties> + <help>Show log for DHCPv6 server</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit isc-dhcp-server6.service</command> + </node> + <node name="client"> + <properties> + <help>Show DHCPv6 client logs</help> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show DHCPv6 client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --follow --boot --unit "dhcp6c@$6.service"</command> + </tagNode> + </children> + </node> + </children> + </node> + <leafNode name="kernel"> + <properties> + <help>Monitor last lines of Linux Kernel log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --dmesg</command> + </leafNode> + <node name="pppoe"> + <properties> + <help>Monitor last lines of PPPoE log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@pppoe*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of PPPoE log for specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$6.service"</command> + </tagNode> + </children> </node> <node name="protocol"> <properties> @@ -23,67 +106,67 @@ <properties> <help>Monitor log for OSPF</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/ospfd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospfd</command> </leafNode> <leafNode name="ospfv3"> <properties> <help>Monitor log for OSPF for IPv6</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/ospf6d</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ospf6d</command> </leafNode> <leafNode name="bgp"> <properties> <help>Monitor log for BGP</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/bgpd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bgpd</command> </leafNode> <leafNode name="rip"> <properties> <help>Monitor log for RIP</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/ripd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripd</command> </leafNode> <leafNode name="ripng"> <properties> <help>Monitor log for RIPng</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/ripngd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ripngd</command> </leafNode> <leafNode name="static"> <properties> <help>Monitor log for static route</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/staticd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/staticd</command> </leafNode> <leafNode name="multicast"> <properties> <help>Monitor log for Multicast protocol</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/pimd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/pimd</command> </leafNode> <leafNode name="isis"> <properties> <help>Monitor log for ISIS</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/isisd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/isisd</command> </leafNode> <leafNode name="nhrp"> <properties> <help>Monitor log for NHRP</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/nhrpd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/nhrpd</command> </leafNode> <leafNode name="bfd"> <properties> <help>Monitor log for BFD</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/bfdd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/bfdd</command> </leafNode> <leafNode name="mpls"> <properties> <help>Monitor log for MPLS</help> </properties> - <command>journalctl --follow --boot /usr/lib/frr/ldpd</command> + <command>journalctl --follow --no-hostname --boot /usr/lib/frr/ldpd</command> </leafNode> </children> </node> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 15bbc7f42..954369712 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -179,9 +179,9 @@ </tagNode> <leafNode name="kernel"> <properties> - <help>Show messages in kernel ring buffer</help> + <help>Show log for Linux Kernel</help> </properties> - <command>sudo dmesg</command> + <command>journalctl --no-hostname --boot --dmesg</command> </leafNode> <leafNode name="lldp"> <properties> @@ -212,6 +212,23 @@ </tagNode> </children> </node> + <node name="pppoe"> + <properties> + <help>Show log for PPPoE</help> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@pppoe*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show PPPoE log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -t pppoe</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@$6.service"</command> + </tagNode> + </children> + </node> <node name="protocol"> <properties> <help>Show log for Routing Protocols</help> diff --git a/python/vyos/base.py b/python/vyos/base.py index fd22eaccd..78067d5b2 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -1,4 +1,4 @@ -# Copyright 2018-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2018-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 @@ -15,6 +15,12 @@ from textwrap import fill +class Warning(): + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + print(f'\nWARNING: {message}') + class DeprecationWarning(): def __init__(self, message): # Reformat the message and trim it to 72 characters in length diff --git a/python/vyos/config.py b/python/vyos/config.py index 858c7bdd7..287fd2ed1 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -156,26 +156,28 @@ class Config(object): """ if self._session_config is None: return False + + # Assume the path is a node path first if self._session_config.exists(self._make_path(path)): return True - # libvyosconfig exists() works only for _nodes_, not _values_ - # libvyattacfg also worked for values, so we emulate that case here - if isinstance(path, str): - path = re.split(r'\s+', path) - path_without_value = path[:-1] - path_str = " ".join(path_without_value) - try: - value = self._session_config.return_value(self._make_path(path_str)) - values = self._session_config.return_values(self._make_path(path_str)) - except vyos.configtree.ConfigTreeError: - # node/value doesn't exist - return False - if value and path[-1] == value: - return True - if isinstance(values, list) and path[-1] in values: - return True + else: + # If that check fails, it may mean the path has a value at the end. + # libvyosconfig exists() works only for _nodes_, not _values_ + # libvyattacfg also worked for values, so we emulate that case here + if isinstance(path, str): + path = re.split(r'\s+', path) + path_without_value = path[:-1] + try: + # return_values() is safe to use with single-value nodes, + # it simply returns a single-item list in that case. + values = self._session_config.return_values(self._make_path(path_without_value)) - return False + # If we got this far, the node does exist and has values, + # so we need to check if it has the value in question among its values. + return (path[-1] in values) + except vyos.configtree.ConfigTreeError: + # Even the parent node doesn't exist at all + return False def session_changed(self): """ @@ -402,26 +404,29 @@ class Config(object): """ if self._running_config is None: return False + + # Assume the path is a node path first if self._running_config.exists(self._make_path(path)): return True - # libvyosconfig exists() works only for _nodes_, not _values_ - # libvyattacfg also worked for values, so we emulate that case here - if isinstance(path, str): - path = re.split(r'\s+', path) - path_without_value = path[:-1] - path_str = " ".join(path_without_value) - try: - value = self._running_config.return_value(self._make_path(path_str)) - values = self._running_config.return_values(self._make_path(path_str)) - except vyos.configtree.ConfigTreeError: - # node/value doesn't exist - return False - if value and path[-1] == value: - return True - if isinstance(values, list) and path[-1] in values: - return True + else: + # If that check fails, it may mean the path has a value at the end. + # libvyosconfig exists() works only for _nodes_, not _values_ + # libvyattacfg also worked for values, so we emulate that case here + if isinstance(path, str): + path = re.split(r'\s+', path) + path_without_value = path[:-1] + try: + # return_values() is safe to use with single-value nodes, + # it simply returns a single-item list in that case. + values = self._running_config.return_values(self._make_path(path_without_value)) + + # If we got this far, the node does exist and has values, + # so we need to check if it has the value in question among its values. + return (path[-1] in values) + except vyos.configtree.ConfigTreeError: + # Even the parent node doesn't exist at all + return False - return False def return_effective_value(self, path, default=None): """ diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 551c27b67..04ddc10e9 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -104,6 +104,11 @@ def list_diff(first, second): second = set(second) return [item for item in first if item not in second] +def is_node_changed(conf, path): + from vyos.configdiff import get_config_diff + D = get_config_diff(conf, key_mangling=('-', '_')) + return D.is_node_changed(path) + def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been @@ -114,7 +119,6 @@ def leaf_node_changed(conf, path): """ from vyos.configdiff import get_config_diff D = get_config_diff(conf, key_mangling=('-', '_')) - D.set_level(conf.get_level()) (new, old) = D.get_value_diff(path) if new != old: if isinstance(old, dict): @@ -144,12 +148,11 @@ def node_changed(conf, path, key_mangling=None, recursive=False): """ from vyos.configdiff import get_config_diff, Diff D = get_config_diff(conf, key_mangling) - D.set_level(conf.get_level()) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE, recursive=recursive)['delete'].keys() return list(keys) -def get_removed_vlans(conf, dict): +def get_removed_vlans(conf, path, dict): """ Common function to parse a dictionary retrieved via get_config_dict() and determine any added/removed VLAN interfaces - be it 802.1q or Q-in-Q. @@ -159,16 +162,17 @@ def get_removed_vlans(conf, dict): # Check vif, vif-s/vif-c VLAN interfaces for removal D = get_config_diff(conf, key_mangling=('-', '_')) D.set_level(conf.get_level()) + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + keys = D.get_child_nodes_diff(path + ['vif'], expand_nodes=Diff.DELETE)['delete'].keys() if keys: dict['vif_remove'] = [*keys] # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + keys = D.get_child_nodes_diff(path + ['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() if keys: dict['vif_s_remove'] = [*keys] for vif in dict.get('vif_s', {}).keys(): - keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + keys = D.get_child_nodes_diff(path + ['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() if keys: dict['vif_s'][vif]['vif_c_remove'] = [*keys] return dict @@ -212,10 +216,6 @@ def is_member(conf, interface, intftype=None): intftype = intftypes if intftype == None else [intftype] - # set config level to root - old_level = conf.get_level() - conf.set_level([]) - for iftype in intftype: base = ['interfaces', iftype] for intf in conf.list_nodes(base): @@ -225,7 +225,6 @@ def is_member(conf, interface, intftype=None): get_first_key=True, no_tag_node_value_mangle=True) ret_val.update({intf : tmp}) - old_level = conf.set_level(old_level) return ret_val def is_mirror_intf(conf, interface, direction=None): @@ -247,8 +246,6 @@ def is_mirror_intf(conf, interface, direction=None): direction = directions if direction == None else [direction] ret_val = None - old_level = conf.get_level() - conf.set_level([]) base = ['interfaces'] for dir in direction: @@ -262,7 +259,6 @@ def is_mirror_intf(conf, interface, direction=None): get_first_key=True) ret_val = {intf : tmp} - old_level = conf.set_level(old_level) return ret_val def has_vlan_subinterface_configured(conf, intf): @@ -276,15 +272,11 @@ def has_vlan_subinterface_configured(conf, intf): from vyos.ifconfig import Section ret = False - old_level = conf.get_level() - conf.set_level([]) - intfpath = ['interfaces', Section.section(intf), intf] if ( conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])): ret = True - conf.set_level(old_level) return ret def is_source_interface(conf, interface, intftype=None): @@ -306,11 +298,6 @@ def is_source_interface(conf, interface, intftype=None): 'have a source-interface') intftype = intftypes if intftype == None else [intftype] - - # set config level to root - old_level = conf.get_level() - conf.set_level([]) - for it in intftype: base = ['interfaces', it] for intf in conf.list_nodes(base): @@ -319,7 +306,6 @@ def is_source_interface(conf, interface, intftype=None): ret_val = intf break - old_level = conf.set_level(old_level) return ret_val def get_dhcp_interfaces(conf, vrf=None): @@ -330,40 +316,67 @@ def get_dhcp_interfaces(conf, vrf=None): if not dict: return dhcp_interfaces - def check_dhcp(config, ifname): + def check_dhcp(config): + ifname = config['ifname'] tmp = {} if 'address' in config and 'dhcp' in config['address']: options = {} - if 'dhcp_options' in config and 'default_route_distance' in config['dhcp_options']: - options.update({'distance' : config['dhcp_options']['default_route_distance']}) + if dict_search('dhcp_options.default_route_distance', config) != None: + options.update({'dhcp_options' : config['dhcp_options']}) if 'vrf' in config: if vrf is config['vrf']: tmp.update({ifname : options}) else: tmp.update({ifname : options}) + return tmp for section, interface in dict.items(): for ifname in interface: + # always reset config level, as get_interface_dict() will alter it + conf.set_level([]) # we already have a dict representation of the config from get_config_dict(), # but with the extended information from get_interface_dict() we also # get the DHCP client default-route-distance default option if not specified. - ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) + _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) - tmp = check_dhcp(ifconfig, ifname) + tmp = check_dhcp(ifconfig) dhcp_interfaces.update(tmp) # check per VLAN interfaces for vif, vif_config in ifconfig.get('vif', {}).items(): - tmp = check_dhcp(vif_config, f'{ifname}.{vif}') + tmp = check_dhcp(vif_config) dhcp_interfaces.update(tmp) # check QinQ VLAN interfaces - for vif_s, vif_s_config in ifconfig.get('vif-s', {}).items(): - tmp = check_dhcp(vif_s_config, f'{ifname}.{vif_s}') + for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items(): + tmp = check_dhcp(vif_s_config) dhcp_interfaces.update(tmp) - for vif_c, vif_c_config in vif_s_config.get('vif-c', {}).items(): - tmp = check_dhcp(vif_c_config, f'{ifname}.{vif_s}.{vif_c}') + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + tmp = check_dhcp(vif_c_config) dhcp_interfaces.update(tmp) return dhcp_interfaces +def get_pppoe_interfaces(conf, vrf=None): + """ Common helper functions to retrieve all interfaces from current CLI + sessions that have DHCP configured. """ + pppoe_interfaces = {} + for ifname in conf.list_nodes(['interfaces', 'pppoe']): + # always reset config level, as get_interface_dict() will alter it + conf.set_level([]) + # we already have a dict representation of the config from get_config_dict(), + # but with the extended information from get_interface_dict() we also + # get the DHCP client default-route-distance default option if not specified. + ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname) + + options = {} + if 'default_route_distance' in ifconfig: + options.update({'default_route_distance' : ifconfig['default_route_distance']}) + if 'no_default_route' in ifconfig: + options.update({'no_default_route' : {}}) + if 'vrf' in ifconfig: + if vrf is ifconfig['vrf']: pppoe_interfaces.update({ifname : options}) + else: pppoe_interfaces.update({ifname : options}) + + return pppoe_interfaces + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mangle the interfaces configuration @@ -373,7 +386,6 @@ def get_interface_dict(config, base, ifname=''): Return a dictionary with the necessary interface config keys. """ - if not ifname: from vyos import ConfigError # determine tagNode instance @@ -390,9 +402,8 @@ def get_interface_dict(config, base, ifname=''): for vif in ['vif', 'vif_s']: if vif in default_values: del default_values[vif] - # setup config level which is extracted in get_removed_vlans() - config.set_level(base + [ifname]) - dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True, + dict = config.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) # Check if interface has been removed. We must use exists() as @@ -400,8 +411,8 @@ def get_interface_dict(config, base, ifname=''): # node like the following exists. # +macsec macsec1 { # +} - if not config.exists([]): - dict.update({'deleted' : ''}) + if not config.exists(base + [ifname]): + dict.update({'deleted' : {}}) # Add interface instance name into dictionary dict.update({'ifname': ifname}) @@ -428,7 +439,7 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2665: blend in proper DHCPv6-PD default values dict = T2665_set_dhcpv6pd_defaults(dict) - address = leaf_node_changed(config, ['address']) + address = leaf_node_changed(config, base + [ifname, 'address']) if address: dict.update({'address_old' : address}) # Check if we are a member of a bridge device @@ -459,10 +470,10 @@ def get_interface_dict(config, base, ifname=''): tmp = is_member(config, dict['source_interface'], 'bonding') if tmp: dict.update({'source_interface_is_bond_member' : tmp}) - mac = leaf_node_changed(config, ['mac']) + mac = leaf_node_changed(config, base + [ifname, 'mac']) if mac: dict.update({'mac_old' : mac}) - eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) + eui64 = leaf_node_changed(config, base + [ifname, 'ipv6', 'address', 'eui64']) if eui64: tmp = dict_search('ipv6.address', dict) if not tmp: @@ -474,6 +485,9 @@ def get_interface_dict(config, base, ifname=''): # identical for all types of VLAN interfaces as they all include the same # XML definitions which hold the defaults. for vif, vif_config in dict.get('vif', {}).items(): + # Add subinterface name to dictionary + dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'}) + default_vif_values = defaults(base + ['vif']) # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely # remove the default values from the dict. @@ -483,7 +497,7 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - address = leaf_node_changed(config, ['vif', vif, 'address']) + address = leaf_node_changed(config, base + [ifname, 'vif', vif, 'address']) if address: dict['vif'][vif].update({'address_old' : address}) dict['vif'][vif] = dict_merge(default_vif_values, dict['vif'][vif]) @@ -505,6 +519,9 @@ def get_interface_dict(config, base, ifname=''): if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''}) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): + # Add subinterface name to dictionary + dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'}) + default_vif_s_values = defaults(base + ['vif-s']) # XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c'] @@ -517,7 +534,7 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - address = leaf_node_changed(config, ['vif-s', vif_s, 'address']) + address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'address']) if address: dict['vif_s'][vif_s].update({'address_old' : address}) dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, @@ -541,6 +558,9 @@ def get_interface_dict(config, base, ifname=''): if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''}) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + # Add subinterface name to dictionary + dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'}) + default_vif_c_values = defaults(base + ['vif-s', 'vif-c']) # XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely @@ -551,7 +571,7 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - address = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'address']) + address = leaf_node_changed(config, base + [ifname, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update( {'address_old' : address}) @@ -578,8 +598,8 @@ def get_interface_dict(config, base, ifname=''): if dhcp: dict['vif_s'][vif_s]['vif_c'][vif_c].update({'dhcp_options_changed' : ''}) # Check vif, vif-s/vif-c VLAN interfaces for removal - dict = get_removed_vlans(config, dict) - return dict + dict = get_removed_vlans(config, base + [ifname], dict) + return ifname, dict def get_vlan_ids(interface): """ diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 1062d51ee..438485d98 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.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 @@ -205,10 +205,10 @@ def verify_mirror_redirect(config): raise ConfigError(f'Requested redirect interface "{redirect_ifname}" '\ 'does not exist!') - if dict_search('traffic_policy.in', config) != None: + if ('mirror' in config or 'redirect' in config) and dict_search('traffic_policy.in', config) is not None: # XXX: support combination of limiting and redirect/mirror - this is an # artificial limitation - raise ConfigError('Can not use ingress policy tigether with mirror or redirect!') + raise ConfigError('Can not use ingress policy together with mirror or redirect!') def verify_authentication(config): """ diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index 7cb3968df..276c34cd7 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -42,7 +42,7 @@ class GeneveIf(Interface): # arguments used by iproute2. For more information please refer to: # - https://man7.org/linux/man-pages/man8/ip-link.8.html mapping = { - 'parameters.ip.dont_fragment': 'df set', + 'parameters.ip.df' : 'df', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', 'parameters.ipv6.flowlabel' : 'flowlabel', diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 6b0f08fd4..22441d1d2 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -736,8 +736,9 @@ class Interface(Control): if all_rp_filter == 1: global_setting = 'strict' elif all_rp_filter == 2: global_setting = 'loose' - print(f'WARNING: Global source-validation is set to "{global_setting}\n"' \ - 'this overrides per interface setting!') + from vyos.base import Warning + Warning(f'Global source-validation is set to "{global_setting} '\ + f'this overrides per interface setting!') tmp = self.get_interface('rp_filter') if int(tmp) == value: @@ -1243,8 +1244,8 @@ class Interface(Control): tmp = {'dhcp_options' : { 'host_name' : hostname}} self._config = dict_merge(tmp, self._config) - render(options_file, 'dhcp-client/daemon-options.tmpl', self._config) - render(config_file, 'dhcp-client/ipv4.tmpl', self._config) + render(options_file, 'dhcp-client/daemon-options.j2', self._config) + render(config_file, 'dhcp-client/ipv4.j2', self._config) # When the DHCP client is restarted a brief outage will occur, as # the old lease is released a new one is acquired (T4203). We will @@ -1274,8 +1275,7 @@ class Interface(Control): systemd_service = f'dhcp6c@{ifname}.service' if enable and 'disable' not in self._config: - render(config_file, 'dhcp-client/ipv6.tmpl', - self._config) + render(config_file, 'dhcp-client/ipv6.j2', self._config) # We must ignore any return codes. This is required to enable # DHCPv6-PD for interfaces which are yet not up and running. @@ -1587,12 +1587,10 @@ class Interface(Control): tmp['source_interface'] = ifname tmp['vlan_id'] = vif_s_id - vif_s_ifname = f'{ifname}.{vif_s_id}' - vif_s_config['ifname'] = vif_s_ifname - # It is not possible to change the VLAN encapsulation protocol # "on-the-fly". For this "quirk" we need to actively delete and # re-create the VIF-S interface. + vif_s_ifname = f'{ifname}.{vif_s_id}' if self.exists(vif_s_ifname): cur_cfg = get_interface_config(vif_s_ifname) protocol = dict_search('linkinfo.info_data.protocol', cur_cfg).lower() @@ -1614,7 +1612,6 @@ class Interface(Control): tmp['vlan_id'] = vif_c_id vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}' - vif_c_config['ifname'] = vif_c_ifname c_vlan = VLANIf(vif_c_ifname, **tmp) c_vlan.update(vif_c_config) @@ -1625,10 +1622,7 @@ class Interface(Control): # create/update 802.1q VLAN interfaces for vif_id, vif_config in config.get('vif', {}).items(): - vif_ifname = f'{ifname}.{vif_id}' - vif_config['ifname'] = vif_ifname - tmp = deepcopy(VLANIf.get_config()) tmp['source_interface'] = ifname tmp['vlan_id'] = vif_id diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 1d13264bf..63ffc8069 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -27,12 +27,13 @@ class PPPoEIf(Interface): }, } - def _remove_routes(self, vrf=''): + def _remove_routes(self, vrf=None): # Always delete default routes when interface is removed + vrf_cmd = '' if vrf: - vrf = f'-c "vrf {vrf}"' - self._cmd(f'vtysh -c "conf t" {vrf} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"') - self._cmd(f'vtysh -c "conf t" {vrf} -c "no ipv6 route ::/0 {self.ifname} tag 210"') + vrf_cmd = f'-c "vrf {vrf}"' + self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"') + self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ipv6 route ::/0 {self.ifname} tag 210"') def remove(self): """ @@ -44,11 +45,11 @@ class PPPoEIf(Interface): >>> i = Interface('pppoe0') >>> i.remove() """ - + vrf = None tmp = get_interface_config(self.ifname) - vrf = '' if 'master' in tmp: - self._remove_routes(tmp['master']) + vrf = tmp['master'] + self._remove_routes(vrf) # remove bond master which places members in disabled state super().remove() @@ -84,10 +85,12 @@ class PPPoEIf(Interface): self._config = config # remove old routes from an e.g. old VRF assignment - vrf = '' - if 'vrf_old' in config: - vrf = config['vrf_old'] - self._remove_routes(vrf) + if 'shutdown_required': + vrf = None + tmp = get_interface_config(self.ifname) + if 'master' in tmp: + vrf = tmp['master'] + self._remove_routes(vrf) # DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do # not require an 'address dhcpv6' CLI option as with other interfaces @@ -98,54 +101,15 @@ class PPPoEIf(Interface): super().update(config) - if 'default_route' not in config or config['default_route'] == 'none': - return - - # - # Set default routes pointing to pppoe interface - # - vrf = '' - sed_opt = '^ip route' - - install_v4 = True - install_v6 = True - # generate proper configuration string when VRFs are in use + vrf = '' if 'vrf' in config: tmp = config['vrf'] vrf = f'-c "vrf {tmp}"' - sed_opt = f'vrf {tmp}' - - if config['default_route'] == 'auto': - # only add route if there is no default route present - tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"') - for line in tmp.splitlines(): - line = line.lstrip() - if line.startswith('ip route 0.0.0.0/0'): - install_v4 = False - continue - - if 'ipv6' in config and line.startswith('ipv6 route ::/0'): - install_v6 = False - continue - - elif config['default_route'] == 'force': - # Force means that all static routes are replaced with the ones from this interface - tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"') - for line in tmp.splitlines(): - if self.ifname in line: - # It makes no sense to remove a route with our interface and the later re-add it. - # This will only make traffic disappear - which is a no-no! - continue - - line = line.lstrip() - if line.startswith('ip route 0.0.0.0/0'): - self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"') - - if 'ipv6' in config and line.startswith('ipv6 route ::/0'): - self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"') - - if install_v4: - self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210"') - if install_v6 and 'ipv6' in config: - self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210"') + + if 'no_default_route' not in config: + # Set default route(s) pointing to PPPoE interface + distance = config['default_route_distance'] + self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210 {distance}"') + if 'ipv6' in config: + self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210 {distance}"') diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 516a19f24..5baff10a9 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -57,7 +57,7 @@ class VXLANIf(Interface): 'group' : 'group', 'external' : 'external', 'gpe' : 'gpe', - 'parameters.ip.dont_fragment': 'df set', + 'parameters.ip.df' : 'df', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', 'parameters.ipv6.flowlabel' : 'flowlabel', diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 493feed5b..3d62d269c 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -11,11 +11,35 @@ interfaces { smp-affinity auto speed auto } + ethernet eth2 { + address 100.100.0.1/24 + duplex auto + smp-affinity auto + speed auto + } loopback lo { } } protocols { static { + arp 192.168.0.20 { + hwaddr 00:50:00:00:00:20 + } + arp 192.168.0.30 { + hwaddr 00:50:00:00:00:30 + } + arp 192.168.0.40 { + hwaddr 00:50:00:00:00:40 + } + arp 100.100.0.2 { + hwaddr 00:50:00:00:02:02 + } + arp 100.100.0.3 { + hwaddr 00:50:00:00:02:03 + } + arp 100.100.0.4 { + hwaddr 00:50:00:00:02:04 + } route 0.0.0.0/0 { next-hop 100.64.0.1 { } diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index ba5acf5d6..816ba6dcd 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -78,18 +78,25 @@ class BasicInterfaceTest: # choose IPv6 minimum MTU value for tests - this must always work _mtu = '1280' - def setUp(self): + @classmethod + def setUpClass(cls): + super(BasicInterfaceTest.TestCase, cls).setUpClass() + # Setup mirror interfaces for SPAN (Switch Port Analyzer) - for span in self._mirror_interfaces: + for span in cls._mirror_interfaces: section = Section.section(span) - self.cli_set(['interfaces', section, span]) + cls.cli_set(cls, ['interfaces', section, span]) - def tearDown(self): + @classmethod + def tearDownClass(cls): # Tear down mirror interfaces for SPAN (Switch Port Analyzer) - for span in self._mirror_interfaces: + for span in cls._mirror_interfaces: section = Section.section(span) - self.cli_delete(['interfaces', section, span]) + cls.cli_delete(cls, ['interfaces', section, span]) + super(BasicInterfaceTest.TestCase, cls).tearDownClass() + + def tearDown(self): self.cli_delete(self._base_path) self.cli_commit() @@ -232,6 +239,7 @@ class BasicInterfaceTest: self.cli_commit() for interface in self._interfaces: + self.assertIn(AF_INET6, ifaddresses(interface)) for addr in ifaddresses(interface)[AF_INET6]: self.assertTrue(is_ipv6_link_local(addr['addr'])) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 5448295fa..37e6b9e7a 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -38,7 +38,7 @@ sysfs_config = { class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestFirewall, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -49,8 +49,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['interfaces', 'ethernet', 'eth0', 'address', '172.16.10.1/24']) - - super(cls, cls).tearDownClass() + super(TestFirewall, cls).tearDownClass() def tearDown(self): self.cli_delete(['interfaces', 'ethernet', 'eth0', 'firewall']) diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 9bb561275..237abb487 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -55,7 +55,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(BondingInterfaceTest, cls).setUpClass() def test_add_single_ip_address(self): super().test_add_single_ip_address() diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index f2e111425..ca0ead9e8 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -56,7 +56,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(BridgeInterfaceTest, cls).setUpClass() def tearDown(self): for intf in self._interfaces: diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py index dedc6fe05..d96ec2c5d 100755 --- a/smoketest/scripts/cli/test_interfaces_dummy.py +++ b/smoketest/scripts/cli/test_interfaces_dummy.py @@ -24,7 +24,7 @@ class DummyInterfaceTest(BasicInterfaceTest.TestCase): cls._base_path = ['interfaces', 'dummy'] cls._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089'] # call base-classes classmethod - super(cls, cls).setUpClass() + super(DummyInterfaceTest, cls).setUpClass() 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 ee7649af8..05d2ae5f5 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -18,13 +18,19 @@ import os import re import unittest +from netifaces import AF_INET +from netifaces import AF_INET6 +from netifaces import ifaddresses + from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.pki import CERT_BEGIN +from vyos.template import is_ipv6 from vyos.util import cmd from vyos.util import process_named_running from vyos.util import read_file +from vyos.validate import is_ipv6_link_local server_ca_root_cert_data = """ MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw @@ -128,7 +134,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): cls._macs[interface] = read_file(f'/sys/class/net/{interface}/address') # call base-classes classmethod - super(cls, cls).setUpClass() + super(EthernetInterfaceTest, cls).setUpClass() def tearDown(self): for interface in self._interfaces: @@ -140,13 +146,20 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.cli_set(self._base_path + [interface, 'speed', 'auto']) self.cli_set(self._base_path + [interface, 'hw-id', self._macs[interface]]) - # Tear down mirror interfaces for SPAN (Switch Port Analyzer) - for span in self._mirror_interfaces: - section = Section.section(span) - self.cli_delete(['interfaces', section, span]) - self.cli_commit() + # Verify that no address remains on the system as this is an eternal + # interface. + for intf in self._interfaces: + self.assertNotIn(AF_INET, ifaddresses(intf)) + # required for IPv6 link-local address + self.assertIn(AF_INET6, ifaddresses(intf)) + for addr in ifaddresses(intf)[AF_INET6]: + # checking link local addresses makes no sense + if is_ipv6_link_local(addr['addr']): + continue + self.assertFalse(is_intf_addr_assigned(intf, addr['addr'])) + def test_offloading_rps(self): # enable RPS on all available CPUs, RPS works woth a CPU bitmask, # where each bit represents a CPU (core/thread). The formula below diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py index 6233ade6e..0e5098aa7 100755 --- a/smoketest/scripts/cli/test_interfaces_geneve.py +++ b/smoketest/scripts/cli/test_interfaces_geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 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 @@ -34,7 +34,7 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase): } cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(GeneveInterfaceTest, cls).setUpClass() def test_geneve_parameters(self): tos = '40' @@ -43,7 +43,7 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase): for option in self._options.get(intf, []): self.cli_set(self._base_path + [intf] + option.split()) - self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set']) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) ttl += 10 diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py index 06ced5c40..aed8e6f15 100755 --- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py +++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py @@ -39,7 +39,7 @@ class L2TPv3InterfaceTest(BasicInterfaceTest.TestCase): } cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(L2TPv3InterfaceTest, cls).setUpClass() def test_add_single_ip_address(self): super().test_add_single_ip_address() diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py index 85b5ca6d6..5ff9c250e 100755 --- a/smoketest/scripts/cli/test_interfaces_loopback.py +++ b/smoketest/scripts/cli/test_interfaces_loopback.py @@ -29,7 +29,7 @@ class LoopbackInterfaceTest(BasicInterfaceTest.TestCase): cls._base_path = ['interfaces', 'loopback'] cls._interfaces = ['lo'] # call base-classes classmethod - super(cls, cls).setUpClass() + super(LoopbackInterfaceTest, cls).setUpClass() def tearDown(self): self.cli_delete(self._base_path) diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index 5b10bfa44..e5e5a558e 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -53,7 +53,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(MACsecInterfaceTest, cls).setUpClass() def test_macsec_encryption(self): # MACsec can be operating in authentication and encryption mode - both diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index f8a6ae986..b2143d16e 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# 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 @@ -37,10 +37,46 @@ PROCESS_NAME = 'openvpn' base_path = ['interfaces', 'openvpn'] -cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0=' -key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww' -dh_data = 'MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFOFxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZUHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS67q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg==' -ovpn_key_data = '443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa431d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e7042a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e65210b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a' +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 +""" + +dh_data = """ +MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFO +FxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZ +UHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+ +Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS6 +7q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv +108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg== +""" + +ovpn_key_data = """ +443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa4 +31d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e704 +2a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043 +e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4 +adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c +9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e6521 +0b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece +4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a +""" remote_port = '1194' protocol = 'udp' @@ -59,20 +95,28 @@ def get_vrf(interface): return tmp class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32']) - self.cli_set(['vrf', 'name', vrf_name, 'table', '12345']) + @classmethod + def setUpClass(cls): + super(TestInterfacesOpenVPN, cls).setUpClass() - self.cli_set(['pki', 'ca', 'ovpn_test', 'certificate', cert_data]) - self.cli_set(['pki', 'certificate', 'ovpn_test', 'certificate', cert_data]) - self.cli_set(['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data]) - self.cli_set(['pki', 'dh', 'ovpn_test', 'parameters', dh_data]) - self.cli_set(['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data]) + cls.cli_set(cls, ['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32']) + cls.cli_set(cls, ['vrf', 'name', vrf_name, 'table', '12345']) + + cls.cli_set(cls, ['pki', 'ca', 'ovpn_test', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'certificate', cert_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'dh', 'ovpn_test', 'parameters', dh_data.replace('\n','')]) + cls.cli_set(cls, ['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data.replace('\n','')]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if]) + cls.cli_delete(cls, ['vrf']) + + super(TestInterfacesOpenVPN, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) - self.cli_delete(['interfaces', 'dummy', dummy_if]) - self.cli_delete(['vrf']) self.cli_commit() def test_openvpn_client_verify(self): @@ -532,6 +576,46 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): self.cli_commit() + def test_openvpn_options(self): + # Ensure OpenVPN process restart on openvpn-option CLI node change + + interface = 'vtun5001' + path = base_path + [interface] + + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['local-address', '10.0.0.2']) + self.cli_set(path + ['remote-address', '192.168.0.3']) + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + + self.cli_commit() + + # Now verify the OpenVPN "raw" option passing. Once an openvpn-option is + # added, modified or deleted from the CLI, OpenVPN daemon must be restarted + cur_pid = process_named_running('openvpn') + self.cli_set(path + ['openvpn-option', '--persist-tun']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + + self.cli_set(path + ['openvpn-option', '--persist-key']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + + self.cli_delete(path + ['openvpn-option']) + self.cli_commit() + + # PID must be different as OpenVPN Must be restarted + new_pid = process_named_running('openvpn') + self.assertNotEqual(cur_pid, new_pid) + cur_pid = new_pid + def test_openvpn_site2site_interfaces_tun(self): # Create two OpenVPN site-to-site interfaces diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 4f1e1ee99..8927121a8 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -34,9 +34,12 @@ def get_config_value(interface, key): # add a classmethod to setup a temporaray PPPoE server for "proper" validation class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self._interfaces = ['pppoe10', 'pppoe20', 'pppoe30'] - self._source_interface = 'eth0' + @classmethod + def setUpClass(cls): + super(PPPoEInterfaceTest, cls).setUpClass() + + cls._interfaces = ['pppoe10', 'pppoe20', 'pppoe30'] + cls._source_interface = 'eth0' def tearDown(self): # Validate PPPoE client process @@ -60,7 +63,6 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + [interface, 'authentication', 'user', user]) self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) - self.cli_set(base_path + [interface, 'default-route', 'auto']) self.cli_set(base_path + [interface, 'mtu', mtu]) self.cli_set(base_path + [interface, 'no-peer-dns']) @@ -136,7 +138,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): for interface in self._interfaces: self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) - self.cli_set(base_path + [interface, 'default-route', 'none']) + self.cli_set(base_path + [interface, 'no-default-route']) self.cli_set(base_path + [interface, 'no-peer-dns']) self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py index adcadc5eb..a51b8d52c 100755 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -48,7 +48,7 @@ class PEthInterfaceTest(BasicInterfaceTest.TestCase): cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(PEthInterfaceTest, cls).setUpClass() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index 99c25c374..44bfbb5f0 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -42,7 +42,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.TestCase): } cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(TunnelInterfaceTest, cls).setUpClass() # create some test interfaces cls.cli_set(cls, ['interfaces', 'dummy', source_if, 'address', cls.local_v4 + '/32']) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index f34b99ea4..058f13721 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -39,7 +39,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): } cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(VXLANInterfaceTest, cls).setUpClass() def test_vxlan_parameters(self): tos = '40' @@ -48,7 +48,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): for option in self._options.get(intf, []): self.cli_set(self._base_path + [intf] + option.split()) - self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'dont-fragment']) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set']) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) ttl += 10 diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py index aaf27a2c4..f3e9670f7 100755 --- a/smoketest/scripts/cli/test_interfaces_wireguard.py +++ b/smoketest/scripts/cli/test_interfaces_wireguard.py @@ -23,10 +23,13 @@ from vyos.configsession import ConfigSessionError base_path = ['interfaces', 'wireguard'] class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32', + @classmethod + def setUpClass(cls): + super(WireGuardInterfaceTest, cls).setUpClass() + + cls._test_addr = ['192.0.2.1/26', '192.0.2.255/31', '192.0.2.64/32', '2001:db8:1::ffff/64', '2001:db8:101::1/112'] - self._interfaces = ['wg0', 'wg1'] + cls._interfaces = ['wg0', 'wg1'] def tearDown(self): self.cli_delete(base_path) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index 4f539a23c..a24f37d8d 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -48,7 +48,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): } cls._interfaces = list(cls._options) # call base-classes classmethod - super(cls, cls).setUpClass() + super(WirelessInterfaceTest, cls).setUpClass() def test_wireless_add_single_ip_address(self): # derived method to check if member interfaces are enslaved properly diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 2e1b8d431..408facfb3 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -30,7 +30,7 @@ dst_path = base_path + ['destination'] class TestNAT(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestNAT, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -59,36 +59,44 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.cli_commit() - tmp = cmd('sudo nft -j list table nat') + 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)): - rule = str(rules[idx]) data = data_json[idx] - 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') + if idx == 0: + self.assertEqual(data['chain'], 'POSTROUTING') + 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]) + jump_target = dict_search('jump.target', data['expr'][1]) + self.assertEqual(jump_target,'VYOS_PRE_SNAT_HOOK') 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) + 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) def test_dnat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] @@ -111,33 +119,42 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.cli_commit() - tmp = cmd('sudo nft -j list table nat') + 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)): - rule = str(rules[idx]) data = data_json[idx] - 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) + if idx == 0: + self.assertEqual(data['chain'], 'PREROUTING') + 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_DNAT_HOOK') else: - self.assertEqual(iface, inbound_iface_200) + + 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) def test_snat_required_translation_address(self): # T2813: Ensure translation address is specified diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index 6b7b49792..aac6a30f9 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -32,7 +32,7 @@ dst_path = base_path + ['destination'] class TestNAT66(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestNAT66, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py index 45a4bd61e..e92123dbc 100755 --- a/smoketest/scripts/cli/test_pki.py +++ b/smoketest/scripts/cli/test_pki.py @@ -129,8 +129,13 @@ xGsJxVHfSKeooUQn6q76sg== """ class TestPKI(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_delete(base_path) + @classmethod + def setUpClass(cls): + super(TestPKI, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) def tearDown(self): self.cli_delete(base_path) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index f1db5350a..6f92457b2 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -154,7 +154,7 @@ peer_group_config = { class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestProtocolsBGP, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -882,5 +882,29 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' rt vpn import {rt_import}', afi_config) self.assertIn(f' exit-address-family', afi_config) + def test_bgp_14_remote_as_peer_group_override(self): + # Peer-group member cannot override remote-as of peer-group + remote_asn = str(int(ASN) + 150) + neighbor = '192.0.2.1' + peer_group = 'bar' + + self.cli_set(base_path + ['local-as', ASN]) + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) + self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group]) + self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn]) + + # Peer-group member cannot override remote-as of peer-group + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['neighbor', neighbor, 'remote-as']) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {neighbor} peer-group {peer_group}', frrconfig) + self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) + self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig) + if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 11c765793..ee4be0b37 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -33,7 +33,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): cls._interfaces = Section.interfaces('ethernet') # call base-classes classmethod - super(cls, cls).setUpClass() + super(TestProtocolsISIS, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index c6751cc42..76e6ca35a 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -68,7 +68,7 @@ profiles = { class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestProtocolsMPLS, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index e433d06d0..e15ea478b 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -35,7 +35,7 @@ log = logging.getLogger('TestProtocolsOSPF') class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestProtocolsOSPF, cls).setUpClass() cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) @@ -47,7 +47,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['policy', 'route-map', route_map]) - super(cls, cls).tearDownClass() + super(TestProtocolsOSPF, cls).tearDownClass() def tearDown(self): # Check for running process diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index 944190089..fa80ad555 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -33,7 +33,7 @@ default_area = '0' class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestProtocolsOSPFv3, cls).setUpClass() cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'route-map', route_map, 'rule', '20', 'action', 'permit']) @@ -45,7 +45,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['policy', 'route-map', route_map]) - super(cls, cls).tearDownClass() + super(TestProtocolsOSPFv3, cls).tearDownClass() def tearDown(self): # Check for running process diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 3ef9c76d8..19efe7786 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -94,13 +94,13 @@ tables = ['80', '81', '82'] class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestProtocolsStatic, cls).setUpClass() cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['vrf']) - super(cls, cls).tearDownClass() + super(TestProtocolsStatic, cls).tearDownClass() def tearDown(self): for route, route_config in routes.items(): diff --git a/smoketest/scripts/cli/test_protocols_static_arp.py b/smoketest/scripts/cli/test_protocols_static_arp.py new file mode 100755 index 000000000..6663ade96 --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_static_arp.py @@ -0,0 +1,88 @@ +#!/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 json +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.util import cmd + +base_path = ['protocols', 'static', 'arp'] +interface = 'eth0' +address = '192.0.2.1/24' + +class TestARP(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestARP, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + # we need a L2 interface with a L3 address to properly configure ARP entries + cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', address]) + + @classmethod + def tearDownClass(cls): + # cleanuop L2 interface + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', address]) + cls.cli_commit(cls) + + super(TestARP, cls).tearDownClass() + + def tearDown(self): + # delete test config + self.cli_delete(base_path) + self.cli_commit() + + def test_static_arp(self): + test_data = { + '192.0.2.10' : { 'lladdr' : '00:01:02:03:04:0a' }, + '192.0.2.11' : { 'lladdr' : '00:01:02:03:04:0b' }, + '192.0.2.12' : { 'lladdr' : '00:01:02:03:04:0c' }, + '192.0.2.13' : { 'lladdr' : '00:01:02:03:04:0d' }, + '192.0.2.14' : { 'lladdr' : '00:01:02:03:04:0e' }, + '192.0.2.15' : { 'lladdr' : '00:01:02:03:04:0f' }, + } + + for host, host_config in test_data.items(): + self.cli_set(base_path + [host, 'hwaddr', host_config['lladdr']]) + + self.cli_commit() + + arp_table = json.loads(cmd('ip -j -4 neigh show')) + for host, host_config in test_data.items(): + # As we search within a list of hosts we need to mark if it was + # found or not. This ensures all hosts from test_data are processed + found = False + for entry in arp_table: + # Other ARP entry - not related to this testcase + if entry['dst'] not in list(test_data): + continue + + if entry['dst'] == host: + self.assertEqual(entry['lladdr'], host_config['lladdr']) + self.assertEqual(entry['dev'], interface) + found = True + + if found == False: + print(entry) + self.assertTrue(found) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 9adb9c042..9c9d6d9f1 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -38,7 +38,7 @@ domain_name = 'vyos.net' class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestServiceDHCPServer, cls).setUpClass() cidr_mask = subnet.split('/')[-1] cls.cli_set(cls, ['interfaces', 'dummy', 'dum8765', 'address', f'{router}/{cidr_mask}']) @@ -46,7 +46,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['interfaces', 'dummy', 'dum8765']) - super(cls, cls).tearDownClass() + super(TestServiceDHCPServer, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index 7177f1505..f83453323 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# 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 @@ -32,16 +32,24 @@ dns_1 = '2001:db8::1' dns_2 = '2001:db8::2' domain = 'vyos.net' nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2'] -interface = 'eth1' +interface = 'eth0' interface_addr = inc_ip(subnet, 1) + '/64' -class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['interfaces', 'ethernet', interface, 'address', interface_addr]) +class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceDHCPv6Server, cls).setUpClass() + cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr]) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'ethernet', interface, 'address', interface_addr]) + cls.cli_commit(cls) + + super(TestServiceDHCPv6Server, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) - self.cli_delete(['interfaces', 'ethernet', interface, 'address', interface_addr]) self.cli_commit() def test_single_pool(self): diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index ddb42e8f8..18f1b8ec5 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -30,7 +30,7 @@ base_path = ['service', 'ids', 'ddos-protection'] class TestServiceIDS(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestServiceIDS, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py index 64fdd9d1b..439c96c33 100755 --- a/smoketest/scripts/cli/test_service_lldp.py +++ b/smoketest/scripts/cli/test_service_lldp.py @@ -37,7 +37,7 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): # call base-classes classmethod - super(cls, cls).setUpClass() + super(TestServiceLLDP, cls).setUpClass() # create a test interfaces for addr in mgmt_addr: @@ -50,7 +50,7 @@ class TestServiceLLDP(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['interfaces', 'dummy', mgmt_if]) - super().tearDownClass() + super(TestServiceLLDP, cls).tearDownClass() def tearDown(self): # service must be running after it was configured diff --git a/smoketest/scripts/cli/test_service_salt.py b/smoketest/scripts/cli/test_service_salt.py new file mode 100755 index 000000000..00a4f2020 --- /dev/null +++ b/smoketest/scripts/cli/test_service_salt.py @@ -0,0 +1,105 @@ +#!/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 unittest + +from socket import gethostname +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.util import process_named_running +from vyos.util import read_file +from vyos.util import cmd + +PROCESS_NAME = 'salt-minion' +SALT_CONF = '/etc/salt/minion' +base_path = ['service', 'salt-minion'] + +interface = 'dum4456' + +class TestServiceSALT(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestServiceSALT, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['interfaces', 'dummy', interface, 'address', '100.64.0.1/16']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['interfaces', 'dummy', interface]) + super(TestServiceSALT, cls).tearDownClass() + + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + # delete testing SALT config + self.cli_delete(base_path) + self.cli_commit() + + # For an unknown reason on QEMU systems (e.g. where smoketests are executed + # from the CI) salt-minion process is not killed by systemd. Apparently + # no issue on VMWare. + if cmd('systemd-detect-virt') != 'kvm': + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_default(self): + servers = ['192.0.2.1', '192.0.2.2'] + + for server in servers: + self.cli_set(base_path + ['master', server]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f' - {server}', conf) + + # defaults + hostname = gethostname() + self.assertIn(f'hash_type: sha256', conf) + self.assertIn(f'id: {hostname}', conf) + self.assertIn(f'mine_interval: 60', conf) + + def test_options(self): + server = '192.0.2.3' + hash = 'sha1' + id = 'foo' + interval = '120' + + self.cli_set(base_path + ['master', server]) + self.cli_set(base_path + ['hash', hash]) + self.cli_set(base_path + ['id', id]) + self.cli_set(base_path + ['interval', interval]) + self.cli_set(base_path + ['source-interface', interface]) + + self.cli_commit() + + # commiconf = read_file() Check configured port + conf = read_file(SALT_CONF) + self.assertIn(f'- {server}', conf) + + # defaults + self.assertIn(f'hash_type: {hash}', conf) + self.assertIn(f'id: {id}', conf) + self.assertIn(f'mine_interval: {interval}', conf) + self.assertIn(f'source_interface_name: {interface}', conf) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index fc24fd54e..e80c689cc 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -49,7 +49,7 @@ def get_config_value(key): class TestSNMPService(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestSNMPService, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 9ed263655..77ad5bc0d 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -46,7 +46,7 @@ def get_config_value(key): class TestServiceSSH(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestServiceSSH, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py index c3e9b600f..e4df88c1e 100755 --- a/smoketest/scripts/cli/test_service_upnp.py +++ b/smoketest/scripts/cli/test_service_upnp.py @@ -37,7 +37,7 @@ ipv6_addr = '2001:db8::1/64' class TestServiceUPnP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestServiceUPnP, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) @@ -51,7 +51,7 @@ class TestServiceUPnP(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, address_base) cls._session.commit() - super(cls, cls).tearDownClass() + super(TestServiceUPnP, cls).tearDownClass() def tearDown(self): # Check for running process diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py index ebbd9fe55..772d6ab16 100755 --- a/smoketest/scripts/cli/test_service_webproxy.py +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -33,14 +33,14 @@ class TestServiceWebProxy(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): # call base-classes classmethod - super(cls, cls).setUpClass() + super(TestServiceWebProxy, cls).setUpClass() # create a test interfaces cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']) @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) - super().tearDownClass() + super(TestServiceWebProxy, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index 84f17bcb0..5a73ebc7d 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -32,7 +32,7 @@ uacctd_conf = '/run/pmacct/uacctd.conf' class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestSystemFlowAccounting, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index c8cf04b7d..e2821687c 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -31,7 +31,7 @@ base_path = ['system', 'ntp'] class TestSystemNTP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestSystemNTP, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 1338fe81c..8a6514d57 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -114,7 +114,7 @@ rgiyCHemtMepq57Pl1Nmj49eEA== class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestVPNIPsec, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -123,8 +123,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): - super(cls, cls).tearDownClass() - + super(TestVPNIPsec, cls).tearDownClass() cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}']) def setUp(self): diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index 1f2c36f0d..bda279342 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -24,8 +24,27 @@ OCSERV_CONF = '/run/ocserv/ocserv.conf' base_path = ['vpn', 'openconnect'] 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 TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase): def tearDown(self): @@ -42,16 +61,16 @@ class TestVpnOpenconnect(VyOSUnitTestSHIM.TestCase): self.cli_delete(pki_path) self.cli_delete(base_path) - self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data]) - self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data]) + self.cli_set(pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')]) + self.cli_set(pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')]) + self.cli_set(pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')]) - self.cli_set(base_path + ["authentication", "local-users", "username", user, "password", password]) - self.cli_set(base_path + ["authentication", "local-users", "username", user, "otp", "key", otp]) - self.cli_set(base_path + ["authentication", "mode", "local", "password-otp"]) - self.cli_set(base_path + ["network-settings", "client-ip-settings", "subnet", "192.0.2.0/24"]) - self.cli_set(base_path + ["ssl", "ca-certificate", 'openconnect']) - self.cli_set(base_path + ["ssl", "certificate", 'openconnect']) + self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'password', password]) + self.cli_set(base_path + ['authentication', 'local-users', 'username', user, 'otp', 'key', otp]) + self.cli_set(base_path + ['authentication', 'mode', 'local', 'password-otp']) + self.cli_set(base_path + ['network-settings', 'client-ip-settings', 'subnet', '192.0.2.0/24']) + self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect']) + self.cli_set(base_path + ['ssl', 'certificate', 'openconnect']) self.cli_commit() diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index c591d6cf5..ff18f7261 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -49,7 +49,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): if not '.' in tmp: cls._interfaces.append(tmp) # call base-classes classmethod - super(cls, cls).setUpClass() + super(VRFTest, cls).setUpClass() def tearDown(self): # delete all VRFs diff --git a/smoketest/scripts/cli/test_zone_policy.py b/smoketest/scripts/cli/test_zone_policy.py index 6e34f3179..2c580e2f1 100755 --- a/smoketest/scripts/cli/test_zone_policy.py +++ b/smoketest/scripts/cli/test_zone_policy.py @@ -23,13 +23,13 @@ from vyos.util import cmd class TestZonePolicy(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): - super(cls, cls).setUpClass() + super(TestZonePolicy, cls).setUpClass() cls.cli_set(cls, ['firewall', 'name', 'smoketest', 'default-action', 'drop']) @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['firewall']) - super(cls, cls).tearDownClass() + super(TestZonePolicy, cls).tearDownClass() def tearDown(self): self.cli_delete(['zone-policy']) diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py index aac07bd80..51a08bee5 100755 --- a/src/conf_mode/arp.py +++ b/src/conf_mode/arp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 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 @@ -13,92 +13,54 @@ # # 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 os -import re -import syslog as sl +from sys import exit from vyos.config import Config +from vyos.configdict import node_changed from vyos.util import call from vyos import ConfigError - from vyos import airbag airbag.enable() -arp_cmd = '/usr/sbin/arp' - -def get_config(): - c = Config() - if not c.exists('protocols static arp'): - return None - - c.set_level('protocols static') - config_data = {} - - for ip_addr in c.list_nodes('arp'): - config_data.update( - { - ip_addr : c.return_value('arp ' + ip_addr + ' hwaddr') - } - ) - - return config_data +def get_config(config=None): + if config: + conf = config + else: + conf = Config() -def generate(c): - c_eff = Config() - c_eff.set_level('protocols static') - c_eff_cnf = {} - for ip_addr in c_eff.list_effective_nodes('arp'): - c_eff_cnf.update( - { - ip_addr : c_eff.return_effective_value('arp ' + ip_addr + ' hwaddr') - } - ) + base = ['protocols', 'static', 'arp'] + arp = conf.get_config_dict(base) + tmp = node_changed(conf, base) + if tmp: arp.update({'removed' : node_changed(conf, base)}) - config_data = { - 'remove' : [], - 'update' : {} - } - ### removal - if c == None: - for ip_addr in c_eff_cnf: - config_data['remove'].append(ip_addr) - else: - for ip_addr in c_eff_cnf: - if not ip_addr in c or c[ip_addr] == None: - config_data['remove'].append(ip_addr) + return arp - ### add/update - if c != None: - for ip_addr in c: - if not ip_addr in c_eff_cnf: - config_data['update'][ip_addr] = c[ip_addr] - if ip_addr in c_eff_cnf: - if c[ip_addr] != c_eff_cnf[ip_addr] and c[ip_addr] != None: - config_data['update'][ip_addr] = c[ip_addr] +def verify(arp): + pass - return config_data +def generate(arp): + pass -def apply(c): - for ip_addr in c['remove']: - sl.syslog(sl.LOG_NOTICE, "arp -d " + ip_addr) - call(f'{arp_cmd} -d {ip_addr} >/dev/null 2>&1') +def apply(arp): + if not arp: + return None - for ip_addr in c['update']: - sl.syslog(sl.LOG_NOTICE, "arp -s " + ip_addr + " " + c['update'][ip_addr]) - updated = c['update'][ip_addr] - call(f'{arp_cmd} -s {ip_addr} {updated}') + if 'removed' in arp: + for host in arp['removed']: + call(f'arp --delete {host}') + if 'arp' in arp: + for host, host_config in arp['arp'].items(): + mac = host_config['hwaddr'] + call(f'arp --set {host} {mac}') if __name__ == '__main__': - try: - c = get_config() - ## syntax verification is done via cli - config = generate(c) - apply(config) - except ConfigError as e: - print(e) - sys.exit(1) + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/containers.py b/src/conf_mode/containers.py index 516671844..3c1a61f19 100755 --- a/src/conf_mode/containers.py +++ b/src/conf_mode/containers.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 @@ -28,7 +28,6 @@ from vyos.configdict import node_changed from vyos.util import call from vyos.util import cmd from vyos.util import run -from vyos.util import read_file from vyos.util import write_file from vyos.template import inc_ip from vyos.template import is_ipv4 @@ -77,10 +76,10 @@ def get_config(config=None): container['name'][name] = dict_merge(default_values, container['name'][name]) # Delete container network, delete containers - tmp = node_changed(conf, ['container', 'network']) + tmp = node_changed(conf, base + ['container', 'network']) if tmp: container.update({'network_remove' : tmp}) - tmp = node_changed(conf, ['container', 'name']) + tmp = node_changed(conf, base + ['container', 'name']) if tmp: container.update({'container_remove' : tmp}) return container diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index 6352e0b4a..4de2ca2f3 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -66,18 +66,19 @@ def generate(relay): if not relay: return None - render(config_file, 'dhcp-relay/dhcrelay.conf.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) return None def apply(relay): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay.service' if not relay: - call('systemctl stop isc-dhcp-relay.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) return None - call('systemctl restart isc-dhcp-relay.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index d27f8d995..52b682d6d 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -286,7 +286,7 @@ def generate(dhcp): # Please see: https://phabricator.vyos.net/T1129 for quoting of the raw # parameters we can pass to ISC DHCPd tmp_file = '/tmp/dhcpd.conf' - render(tmp_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp, + render(tmp_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) # XXX: as we have the ability for a user to pass in "raw" options via VyOS # CLI (see T3544) we now ask ISC dhcpd to test the newly rendered @@ -299,7 +299,7 @@ def generate(dhcp): # Now that we know that the newly rendered configuration is "good" we can # render the "real" configuration - render(config_file, 'dhcp-server/dhcpd.conf.tmpl', dhcp, + render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) return None diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index aea2c3b73..c1bd51f62 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -82,19 +82,20 @@ def generate(relay): if not relay: return None - render(config_file, 'dhcp-relay/dhcrelay6.conf.tmpl', relay) + render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) return None def apply(relay): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-relay6.service' if not relay: # DHCPv6 relay support is removed in the commit - call('systemctl stop isc-dhcp-relay6.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) return None - call('systemctl restart isc-dhcp-relay6.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index be1e6db1e..078ff327c 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -41,7 +41,9 @@ def get_config(config=None): if not conf.exists(base): return None - dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) return dhcpv6 def verify(dhcpv6): @@ -51,7 +53,7 @@ def verify(dhcpv6): # If DHCP is enabled we need one share-network if 'shared_network_name' not in dhcpv6: - raise ConfigError('No DHCPv6 shared networks configured. At least\n' \ + raise ConfigError('No DHCPv6 shared networks configured. At least '\ 'one DHCPv6 shared network must be configured.') # Inspect shared-network/subnet @@ -60,8 +62,9 @@ def verify(dhcpv6): for network, network_config in dhcpv6['shared_network_name'].items(): # A shared-network requires a subnet definition if 'subnet' not in network_config: - raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". At least one\n' \ - 'lease subnet must be configured for each shared network!') + raise ConfigError(f'No DHCPv6 lease subnets configured for "{network}". '\ + 'At least one lease subnet must be configured for '\ + 'each shared network!') for subnet, subnet_config in network_config['subnet'].items(): if 'address_range' in subnet_config: @@ -83,20 +86,20 @@ def verify(dhcpv6): # Stop address must be greater or equal to start address if not ip_address(stop) >= ip_address(start): - raise ConfigError(f'address-range stop address "{stop}" must be greater or equal\n' \ + raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \ f'to the range start address "{start}"!') # DHCPv6 range start address must be unique - two ranges can't # start with the same address - makes no sense if start in range6_start: - raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \ + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool start address "{start}" defined multipe times!') range6_start.append(start) # DHCPv6 range stop address must be unique - two ranges can't # end with the same address - makes no sense if stop in range6_stop: - raise ConfigError(f'Conflicting DHCPv6 lease range:\n' \ + raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool stop address "{stop}" defined multipe times!') range6_stop.append(stop) @@ -112,7 +115,7 @@ def verify(dhcpv6): for prefix, prefix_config in subnet_config['prefix_delegation']['start'].items(): if 'stop' not in prefix_config: - raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}"\n' + raise ConfigError(f'Stop address of delegated IPv6 prefix range "{prefix}" '\ f'must be configured') if 'prefix_length' not in prefix_config: @@ -126,6 +129,10 @@ def verify(dhcpv6): if ip_address(mapping_config['ipv6_address']) not in ip_network(subnet): raise ConfigError(f'static-mapping address for mapping "{mapping}" is not in subnet "{subnet}"!') + if 'vendor_option' in subnet_config: + if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2: + raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') + # Subnets must be unique if subnet in subnets: raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') @@ -149,8 +156,8 @@ def verify(dhcpv6): raise ConfigError('DHCPv6 conflicting subnet ranges: {0} overlaps {1}'.format(net, net2)) if not listen_ok: - raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on\n' \ - 'this machine. At least one subnet6 must be connected such that\n' \ + raise ConfigError('None of the DHCPv6 subnets are connected to a subnet6 on '\ + 'this machine. At least one subnet6 must be connected such that '\ 'DHCPv6 listens on an interface!') @@ -161,20 +168,20 @@ def generate(dhcpv6): if not dhcpv6 or 'disable' in dhcpv6: return None - render(config_file, 'dhcp-server/dhcpdv6.conf.tmpl', dhcpv6) + render(config_file, 'dhcp-server/dhcpdv6.conf.j2', dhcpv6) return None def apply(dhcpv6): # bail out early - looks like removal from running config + service_name = 'isc-dhcp-server6.service' if not dhcpv6 or 'disable' in dhcpv6: # DHCP server is removed in the commit - call('systemctl stop isc-dhcp-server6.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) - return None - call('systemctl restart isc-dhcp-server6.service') + call(f'systemctl restart {service_name}') return None if __name__ == '__main__': diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index fa9b21f20..f1c2d1f43 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -279,10 +279,10 @@ def generate(dns): if not dns: return None - render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', + render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.j2', dns, user=pdns_rec_user, group=pdns_rec_group) - render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', + render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.j2', dns, user=pdns_rec_user, group=pdns_rec_group) for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): @@ -290,7 +290,7 @@ def generate(dns): if 'authoritative_zones' in dns: for zone in dns['authoritative_zones']: - render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', + render(zone['file'], 'dns-forwarding/recursor.zone.conf.j2', zone, user=pdns_rec_user, group=pdns_rec_group) diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index a31e5ed75..06a2f7e15 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -131,7 +131,7 @@ def generate(dyndns): if not dyndns: return None - render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns) + render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns) return None def apply(dyndns): diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f33198a49..de78d53a8 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -21,6 +21,7 @@ from glob import glob from json import loads from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed @@ -225,7 +226,7 @@ def verify_rule(firewall, rule_conf, ipv6): raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule') if not group_obj: - print(f'WARNING: {error_group} "{group_name}" has no members') + Warning(f'{error_group} "{group_name}" has no members!') if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: @@ -395,7 +396,7 @@ def resync_policy_route(): # Update policy route as firewall groups were updated tmp = run(policy_route_conf_script) if tmp > 0: - print('Warning: Failed to re-apply policy route configuration') + Warning('Failed to re-apply policy route configuration!') def apply(firewall): if 'first_install' in firewall: diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 87bad0dc6..93f244f42 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -21,13 +21,14 @@ import copy import vyos.util import vyos.hostsd_client -from vyos import ConfigError +from vyos.base import Warning from vyos.config import Config from vyos.ifconfig import Section from vyos.template import is_ip from vyos.util import cmd from vyos.util import call from vyos.util import process_named_running +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -113,7 +114,7 @@ def verify(hosts): for interface, interface_config in hosts['nameservers_dhcp_interfaces'].items(): # Warnin user if interface does not have DHCP or DHCPv6 configured if not set(interface_config).intersection(['dhcp', 'dhcpv6']): - print(f'WARNING: "{interface}" is not a DHCP interface but uses DHCP name-server option!') + Warning(f'"{interface}" is not a DHCP interface but uses DHCP name-server option!') return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index fb030c9f3..37df3dc92 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -19,6 +19,7 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -92,7 +93,7 @@ def generate(igmp_proxy): # bail out early - service is disabled, but inform user if 'disable' in igmp_proxy: - print('WARNING: IGMP Proxy will be deactivated because it is disabled') + Warning('IGMP Proxy will be deactivated because it is disabled') return None render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index ad5a0f499..4167594e3 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -68,7 +68,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'bonding'] - bond = get_interface_dict(conf, base) + ifname, bond = get_interface_dict(conf, base) # To make our own life easier transfor the list of member interfaces # into a dictionary - we will use this to add additional information @@ -81,14 +81,14 @@ def get_config(config=None): if 'mode' in bond: bond['mode'] = get_bond_mode(bond['mode']) - tmp = leaf_node_changed(conf, ['mode']) + tmp = leaf_node_changed(conf, base + [ifname, 'mode']) if tmp: bond.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['lacp-rate']) + tmp = leaf_node_changed(conf, base + [ifname, 'lacp-rate']) if tmp: bond.update({'shutdown_required': {}}) # determine which members have been removed - interfaces_removed = leaf_node_changed(conf, ['member', 'interface']) + interfaces_removed = leaf_node_changed(conf, base + [ifname, 'member', 'interface']) if interfaces_removed: bond.update({'shutdown_required': {}}) if 'member' not in bond: diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index b1f7e6d7c..38ae727c1 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -50,15 +50,15 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'bridge'] - bridge = get_interface_dict(conf, base) + ifname, bridge = get_interface_dict(conf, base) # determine which members have been removed - tmp = node_changed(conf, ['member', 'interface'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + [ifname, 'member', 'interface'], key_mangling=('-', '_')) if tmp: if 'member' in bridge: - bridge['member'].update({'interface_remove': tmp }) + bridge['member'].update({'interface_remove' : tmp }) else: - bridge.update({'member': {'interface_remove': tmp }}) + bridge.update({'member' : {'interface_remove' : tmp }}) if dict_search('member.interface', bridge): # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 4a1eb7b93..e771581e1 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -37,7 +37,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'dummy'] - dummy = get_interface_dict(conf, base) + _, dummy = get_interface_dict(conf, base) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 68f59893a..fec4456fb 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -19,6 +19,7 @@ import os from glob import glob from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_address @@ -64,7 +65,7 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) base = ['interfaces', 'ethernet'] - ethernet = get_interface_dict(conf, base) + _, ethernet = get_interface_dict(conf, base) if 'deleted' not in ethernet: if pki: ethernet['pki'] = pki @@ -142,8 +143,8 @@ def verify(ethernet): raise ConfigError('XDP requires additional TX queues, too few available!') if {'is_bond_member', 'mac'} <= set(ethernet): - print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" ' - f'is a member of bond "{is_bond_member}"'.format(**ethernet)) + Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \ + f'is a member of bond "{is_bond_member}"'.format(**ethernet)) # use common function to verify VLAN configuration verify_vlan_config(ethernet) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 3a668226b..b9cf2fa3c 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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,6 +21,8 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_bridge_delete @@ -41,7 +43,18 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'geneve'] - geneve = get_interface_dict(conf, base) + ifname, geneve = get_interface_dict(conf, base) + + # GENEVE interfaces are picky and require recreation if certain parameters + # change. But a GENEVE interface should - of course - not be re-created if + # it's description or IP address is adjusted. Feels somehow logic doesn't it? + for cli_option in ['remote', 'vni']: + if leaf_node_changed(conf, base + [ifname, cli_option]): + geneve.update({'rebuild_required': {}}) + + if is_node_changed(conf, base + [ifname, 'parameters']): + geneve.update({'rebuild_required': {}}) + return geneve def verify(geneve): @@ -67,11 +80,12 @@ def generate(geneve): def apply(geneve): # Check if GENEVE interface already exists - if geneve['ifname'] in interfaces(): - g = GeneveIf(geneve['ifname']) - # GENEVE is super picky and the tunnel always needs to be recreated, - # thus we can simply always delete it first. - g.remove() + if 'rebuild_required' in geneve or 'delete' in geneve: + if geneve['ifname'] in interfaces(): + g = GeneveIf(geneve['ifname']) + # GENEVE is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + g.remove() if 'deleted' not in geneve: # Finally create the new interface diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 22256bf4f..6a486f969 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -45,15 +45,15 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'l2tpv3'] - l2tpv3 = get_interface_dict(conf, base) + ifname, l2tpv3 = get_interface_dict(conf, base) # To delete an l2tpv3 interface we need the current tunnel and session-id if 'deleted' in l2tpv3: - tmp = leaf_node_changed(conf, ['tunnel-id']) + tmp = leaf_node_changed(conf, base + [ifname, 'tunnel-id']) # leaf_node_changed() returns a list l2tpv3.update({'tunnel_id': tmp[0]}) - tmp = leaf_node_changed(conf, ['session-id']) + tmp = leaf_node_changed(conf, base + [ifname, 'session-id']) l2tpv3.update({'session_id': tmp[0]}) return l2tpv3 diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index e4bc15bb5..08d34477a 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -36,7 +36,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'loopback'] - loopback = get_interface_dict(conf, base) + _, loopback = get_interface_dict(conf, base) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index c71863e61..279dd119b 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -48,7 +48,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'macsec'] - macsec = get_interface_dict(conf, base) + ifname, macsec = get_interface_dict(conf, base) # Check if interface has been removed if 'deleted' in macsec: diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 83d1c6d9b..4750ca3e8 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -32,7 +32,7 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import get_interface_dict -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_bridge_delete from vyos.configverify import verify_mirror_redirect @@ -85,13 +85,12 @@ def get_config(config=None): tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - openvpn = get_interface_dict(conf, base) + ifname, openvpn = get_interface_dict(conf, base) if 'deleted' not in openvpn: openvpn['pki'] = tmp_pki - - tmp = leaf_node_changed(conf, ['openvpn-option']) - if tmp: openvpn['restart_required'] = '' + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. @@ -608,7 +607,7 @@ def generate(openvpn): # Generate User/Password authentication file if 'authentication' in openvpn: - render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn, + render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn, user=user, group=group, permission=0o600) else: # delete old auth file if present @@ -624,16 +623,16 @@ def generate(openvpn): # Our client need's to know its subnet mask ... client_config['server_subnet'] = dict_search('server.subnet', openvpn) - render(client_file, 'openvpn/client.conf.tmpl', client_config, + render(client_file, 'openvpn/client.conf.j2', client_config, user=user, group=group) # we need to support quoting of raw parameters from OpenVPN CLI # see https://phabricator.vyos.net/T1632 - render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, + render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) # Render 20-override.conf for OpenVPN service - render(service_file.format(**openvpn), 'openvpn/service-override.conf.tmpl', openvpn, + render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn, formater=lambda _: _.replace(""", '"'), user=user, group=group) # Reload systemd services config to apply an override call(f'systemctl daemon-reload') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index bfb1fadd5..26daa8381 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,7 +22,9 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict +from vyos.configdict import is_node_changed from vyos.configdict import leaf_node_changed +from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_authentication from vyos.configverify import verify_source_interface from vyos.configverify import verify_interface_exists @@ -47,33 +49,17 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'pppoe'] - pppoe = get_interface_dict(conf, base) + ifname, pppoe = get_interface_dict(conf, base) # We should only terminate the PPPoE session if critical parameters change. # All parameters that can be changed on-the-fly (like interface description) # should not lead to a reconnect! - tmp = leaf_node_changed(conf, ['access-concentrator']) - if tmp: pppoe.update({'shutdown_required': {}}) - - tmp = leaf_node_changed(conf, ['connect-on-demand']) - if tmp: pppoe.update({'shutdown_required': {}}) - - tmp = leaf_node_changed(conf, ['service-name']) - if tmp: pppoe.update({'shutdown_required': {}}) - - tmp = leaf_node_changed(conf, ['source-interface']) - if tmp: pppoe.update({'shutdown_required': {}}) - - tmp = leaf_node_changed(conf, ['vrf']) - # leaf_node_changed() returns a list, as VRF is a non-multi node, there - # will be only one list element - if tmp: pppoe.update({'vrf_old': tmp[0]}) - - tmp = leaf_node_changed(conf, ['authentication', 'user']) - if tmp: pppoe.update({'shutdown_required': {}}) - - tmp = leaf_node_changed(conf, ['authentication', 'password']) - if tmp: pppoe.update({'shutdown_required': {}}) + for options in ['access-concentrator', 'connect-on-demand', 'service-name', + 'source-interface', 'vrf', 'no-default-route', 'authentication']: + if is_node_changed(conf, base + [ifname, options]): + pppoe.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break return pppoe @@ -120,7 +106,7 @@ def apply(pppoe): return None # reconnect should only be necessary when certain config options change, - # like ACS name, authentication, no-peer-dns, source-interface + # like ACS name, authentication ... (see get_config() for details) if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or 'shutdown_required' in pppoe): @@ -130,6 +116,9 @@ def apply(pppoe): p.remove() call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/99-vyos-pppoe-callback + # which triggers PPPoEIf.update() else: if os.path.isdir(f'/sys/class/net/{ifname}'): p = PPPoEIf(ifname) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f2c85554f..1cd3fe276 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -18,7 +18,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict -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 @@ -42,14 +42,14 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'pseudo-ethernet'] - peth = get_interface_dict(conf, base) + ifname, peth = get_interface_dict(conf, base) - mode = leaf_node_changed(conf, ['mode']) - if mode: peth.update({'mode_old' : mode}) + mode = is_node_changed(conf, ['mode']) + if mode: peth.update({'shutdown_required' : {}}) if 'source_interface' in peth: - peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], - peth['source_interface']) + _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], + peth['source_interface']) return peth def verify(peth): diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index f4668d976..eff7f373c 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -48,10 +48,10 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'tunnel'] - tunnel = get_interface_dict(conf, base) + ifname, tunnel = get_interface_dict(conf, base) if 'deleted' not in tunnel: - tmp = leaf_node_changed(conf, ['encapsulation']) + tmp = leaf_node_changed(conf, base + [ifname, 'encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) # We also need to inspect other configured tunnels as there are Kernel diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index f06fdff1b..f4b0436af 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -36,7 +36,7 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'vti'] - vti = get_interface_dict(conf, base) + _, vti = get_interface_dict(conf, base) return vti def verify(vti): diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 0a9b51cac..f44d754ba 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -19,9 +19,11 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 @@ -44,18 +46,19 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'vxlan'] - vxlan = get_interface_dict(conf, base) + ifname, vxlan = get_interface_dict(conf, base) # VXLAN interfaces are picky and require recreation if certain parameters # change. But a VXLAN interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? for cli_option in ['external', 'gpe', 'group', 'port', 'remote', - 'source-address', 'source-interface', 'vni', - 'parameters ip dont-fragment', 'parameters ip tos', - 'parameters ip ttl']: - if leaf_node_changed(conf, cli_option.split()): + 'source-address', 'source-interface', 'vni']: + if leaf_node_changed(conf, base + [ifname, cli_option]): vxlan.update({'rebuild_required': {}}) + if is_node_changed(conf, base + [ifname, 'parameters']): + vxlan.update({'rebuild_required': {}}) + # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) @@ -78,7 +81,7 @@ def verify(vxlan): return None if int(vxlan['mtu']) < 1500: - print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') if 'group' in vxlan: if 'source_interface' not in vxlan: diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index b404375d6..180ffa507 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.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 @@ -46,17 +46,17 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'wireguard'] - wireguard = get_interface_dict(conf, base) + ifname, wireguard = get_interface_dict(conf, base) # Check if a port was changed - wireguard['port_changed'] = leaf_node_changed(conf, ['port']) + wireguard['port_changed'] = leaf_node_changed(conf, base + [ifname, 'port']) # Determine which Wireguard peer has been removed. # Peers can only be removed with their public key! dict = {} - tmp = node_changed(conf, ['peer'], key_mangling=('-', '_')) + tmp = node_changed(conf, base + [ifname, 'peer'], key_mangling=('-', '_')) for peer in (tmp or []): - public_key = leaf_node_changed(conf, ['peer', peer, 'public_key']) + 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) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 7fc22cdab..d34297063 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -76,15 +76,19 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'wireless'] - wifi = get_interface_dict(conf, base) + ifname, wifi = get_interface_dict(conf, base) # Cleanup "delete" default values when required user selectable values are # not defined at all - tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'), + get_first_key=True) if not (dict_search('security.wpa.passphrase', tmp) or dict_search('security.wpa.radius', tmp)): if 'deleted' not in wifi: del wifi['security']['wpa'] + # if 'security' key is empty, drop it too + if len(wifi['security']) == 0: + del wifi['security'] # defaults include RADIUS server specifics per TAG node which need to be # added to individual RADIUS servers instead - so we can simply delete them diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 9a33039a3..e275ace84 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -21,7 +21,7 @@ from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdict import leaf_node_changed +from vyos.configdict import is_node_changed from vyos.configverify import verify_authentication from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mirror_redirect @@ -50,42 +50,36 @@ def get_config(config=None): else: conf = Config() base = ['interfaces', 'wwan'] - wwan = get_interface_dict(conf, base) + ifname, wwan = get_interface_dict(conf, base) # We should only terminate the WWAN session if critical parameters change. # All parameters that can be changed on-the-fly (like interface description) # should not lead to a reconnect! - tmp = leaf_node_changed(conf, ['address']) + tmp = is_node_changed(conf, base + [ifname, 'address']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['apn']) + tmp = is_node_changed(conf, base + [ifname, 'apn']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['disable']) + tmp = is_node_changed(conf, base + [ifname, 'disable']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['vrf']) - # leaf_node_changed() returns a list, as VRF is a non-multi node, there - # will be only one list element - if tmp: wwan.update({'vrf_old': tmp[0]}) - - tmp = leaf_node_changed(conf, ['authentication', 'user']) + tmp = is_node_changed(conf, base + [ifname, 'vrf']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['authentication', 'password']) + tmp = is_node_changed(conf, base + [ifname, 'authentication']) if tmp: wwan.update({'shutdown_required': {}}) - tmp = leaf_node_changed(conf, ['ipv6', 'address', 'autoconf']) + tmp = is_node_changed(conf, base + [ifname, 'ipv6', 'address', 'autoconf']) if tmp: wwan.update({'shutdown_required': {}}) # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. conf.set_level(base) - wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - ifname = wwan['ifname'] # This if-clause is just to be sure - it will always evaluate to true if ifname in wwan['other_interfaces']: del wwan['other_interfaces'][ifname] diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index db8328259..2bb615eb7 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -18,6 +18,7 @@ import os from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.validate import is_addr_assigned @@ -84,11 +85,11 @@ def verify(lldp): if 'management_address' in lldp: for address in lldp['management_address']: - message = f'WARNING: LLDP management address "{address}" is invalid' + message = f'LLDP management address "{address}" is invalid' if is_loopback_addr(address): - print(f'{message} - loopback address') + Warning(f'{message} - loopback address') elif not is_addr_assigned(address): - print(f'{message} - not assigned to any interface') + Warning(f'{message} - not assigned to any interface') if 'interface' in lldp: for interface, interface_config in lldp['interface'].items(): diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 14ca7bc94..8aaebf9ff 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -23,6 +23,7 @@ from platform import release as kernel_version from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -142,14 +143,14 @@ def verify(nat): raise ConfigError(f'{err_msg} outbound-interface not specified') if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + 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): - print(f'WARNING: IP address {ip} does not exist on the system!') + 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') @@ -167,7 +168,7 @@ def verify(nat): 'inbound-interface not specified') else: if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + 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: diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 8bf2e8073..1cd15811f 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -21,6 +21,7 @@ import os from sys import exit from netifaces import interfaces +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render @@ -117,12 +118,12 @@ def verify(nat): raise ConfigError(f'{err_msg} outbound-interface not specified') if config['outbound_interface'] not in interfaces(): - raise ConfigError(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') + raise ConfigError(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_ipv6(addr): - raise ConfigError(f'Warning: IPv6 address {addr} is not a valid address') + raise ConfigError(f'IPv6 address {addr} is not a valid address') else: raise ConfigError(f'{err_msg} translation address not specified') @@ -140,7 +141,7 @@ def verify(nat): 'inbound-interface not specified') else: if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): - print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') + Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') return None diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 3d1d7d8c5..09d181d43 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -20,6 +20,7 @@ import re from json import loads from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.template import render from vyos.util import cmd @@ -135,7 +136,7 @@ def verify_rule(policy, name, rule_conf, ipv6): raise ConfigError(f'Invalid {error_group} "{group_name}" on policy route rule') if not group_obj: - print(f'WARNING: {error_group} "{group_name}" has no members') + Warning(f'{error_group} "{group_name}" has no members') if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'): if 'protocol' not in rule_conf: diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index f6d5071c2..a9173ab87 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -138,13 +138,20 @@ def verify(bgp): if asn == bgp['local_as']: raise ConfigError('Cannot have local-as same as BGP AS number') + # Neighbor AS specified for local-as and remote-as can not be the same + if dict_search('remote_as', peer_config) == asn: + raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\ + 'the same as remote-as, this is not allowed!') + # ttl-security and ebgp-multihop can't be used in the same configration if 'ebgp_multihop' in peer_config and 'ttl_security' in peer_config: raise ConfigError('You can not set both ebgp-multihop and ttl-security hops') - # Check if neighbor has both override capability and strict capability match configured at the same time. + # Check if neighbor has both override capability and strict capability match + # configured at the same time. if 'override_capability' in peer_config and 'strict_capability_match' in peer_config: - raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and strict-capability-match configured at the same time!') + raise ConfigError(f'Neighbor "{peer}" cannot have both override-capability and '\ + 'strict-capability-match configured at the same time!') # Check spaces in the password if 'password' in peer_config and ' ' in peer_config['password']: @@ -157,6 +164,12 @@ def verify(bgp): if not verify_remote_as(peer_config, bgp): raise ConfigError(f'Neighbor "{peer}" remote-as must be set!') + # Peer-group member cannot override remote-as of peer-group + if 'peer_group' in peer_config: + peer_group = peer_config['peer_group'] + if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]: + raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!') + # Only checks for ipv4 and ipv6 neighbors # Check if neighbor address is assigned as system interface address vrf = None diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 87432bc1c..58e202928 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -22,6 +22,7 @@ from sys import argv from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import get_dhcp_interfaces +from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_vrf from vyos.template import render_to_string @@ -59,7 +60,9 @@ def get_config(config=None): # T3680 - get a list of all interfaces currently configured to use DHCP tmp = get_dhcp_interfaces(conf, vrf) - if tmp: static['dhcp'] = tmp + if tmp: static.update({'dhcp' : tmp}) + tmp = get_pppoe_interfaces(conf, vrf) + if tmp: static.update({'pppoe' : tmp}) return static diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 841bf6a39..00b889a11 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.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 @@ -16,14 +16,18 @@ import os -from copy import deepcopy from socket import gethostname from sys import exit from urllib3 import PoolManager +from vyos.base import Warning from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configverify import verify_interface_exists from vyos.template import render -from vyos.util import call, chown +from vyos.util import call +from vyos.util import chown +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -32,20 +36,10 @@ airbag.enable() config_file = r'/etc/salt/minion' master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub' -default_config_data = { - 'hash': 'sha256', - 'log_level': 'warning', - 'master' : 'salt', - 'user': 'minion', - 'group': 'vyattacfg', - 'salt_id': gethostname(), - 'mine_interval': '60', - 'verify_master_pubkey_sign': 'false', - 'master_key': '' -} +user='minion' +group='vyattacfg' def get_config(config=None): - salt = deepcopy(default_config_data) if config: conf = config else: @@ -54,44 +48,44 @@ def get_config(config=None): if not conf.exists(base): return None - else: - conf.set_level(base) - if conf.exists(['hash']): - salt['hash'] = conf.return_value(['hash']) + salt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # ID default is dynamic thus we can not use defaults() + if 'id' not in salt: + salt['id'] = gethostname() + # 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) + salt = dict_merge(default_values, salt) - if conf.exists(['master']): - salt['master'] = conf.return_values(['master']) - - if conf.exists(['id']): - salt['salt_id'] = conf.return_value(['id']) + if not conf.exists(base): + return None + else: + conf.set_level(base) - if conf.exists(['user']): - salt['user'] = conf.return_value(['user']) + return salt - if conf.exists(['interval']): - salt['interval'] = conf.return_value(['interval']) +def verify(salt): + if not salt: + return None - if conf.exists(['master-key']): - salt['master_key'] = conf.return_value(['master-key']) - salt['verify_master_pubkey_sign'] = 'true' + if 'hash' in salt and salt['hash'] == 'sha1': + Warning('Do not use sha1 hashing algorithm, upgrade to sha256 or later!') - return salt + if 'source_interface' in salt: + verify_interface_exists(salt['source_interface']) -def verify(salt): return None def generate(salt): if not salt: return None - render(config_file, 'salt-minion/minion.tmpl', salt, - user=salt['user'], group=salt['group']) + render(config_file, 'salt-minion/minion.j2', salt, user=user, group=group) if not os.path.exists(master_keyfile): - if salt['master_key']: + if 'master_key' in salt: req = PoolManager().request('GET', salt['master_key'], preload_content=False) - with open(master_keyfile, 'wb') as f: while True: data = req.read(1024) @@ -100,18 +94,19 @@ def generate(salt): f.write(data) req.release_conn() - chown(master_keyfile, salt['user'], salt['group']) + chown(master_keyfile, user, group) return None def apply(salt): + service_name = 'salt-minion.service' if not salt: # Salt removed from running config - call('systemctl stop salt-minion.service') + call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) else: - call('systemctl restart salt-minion.service') + call(f'systemctl restart {service_name}') return None diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 4fc6a4517..e35bb8a0c 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -18,6 +18,7 @@ import os from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf @@ -149,7 +150,7 @@ def verify(snmp): tmp = extension_opt['script'] if not os.path.isfile(tmp): - print(f'WARNING: script "{tmp}" does not exist!') + Warning(f'script "{tmp}" does not exist!') else: chmod_755(extension_opt['script']) @@ -158,7 +159,7 @@ def verify(snmp): # We only wan't to configure addresses that exist on the system. # Hint the user if they don't exist if not is_addr_assigned(address): - print(f'WARNING: SNMP listen address "{address}" not configured!') + Warning(f'SNMP listen address "{address}" not configured!') if 'trap_target' in snmp: for trap, trap_config in snmp['trap_target'].items(): diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index ef726670c..95050624e 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -22,6 +22,7 @@ from copy import deepcopy from glob import glob from sys import exit +from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf @@ -68,8 +69,8 @@ def verify(tftpd): for address, address_config in tftpd['listen_address'].items(): if not is_addr_assigned(address): - print(f'WARNING: TFTP server listen address "{address}" not ' \ - 'assigned to any interface!') + Warning(f'TFTP server listen address "{address}" not ' \ + 'assigned to any interface!') verify_vrf(address_config) return None diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 99b82ca2d..dc134fd1f 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -553,13 +553,15 @@ def generate(ipsec): if not local_prefixes or not remote_prefixes: continue - passthrough = [] + passthrough = None for local_prefix in local_prefixes: for remote_prefix in remote_prefixes: local_net = ipaddress.ip_network(local_prefix) remote_net = ipaddress.ip_network(remote_prefix) if local_net.overlaps(remote_net): + if passthrough is None: + passthrough = [] passthrough.append(local_prefix) ipsec['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback index bb918a468..fa1917ab1 100755 --- a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback +++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback @@ -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 @@ -23,14 +23,9 @@ from sys import argv from sys import exit -from syslog import syslog -from syslog import openlog -from syslog import LOG_PID -from syslog import LOG_INFO - from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict from vyos.ifconfig import PPPoEIf -from vyos.util import read_file # When the ppp link comes up, this script is called with the following # parameters @@ -45,15 +40,10 @@ if (len(argv) < 7): exit(1) interface = argv[6] -dialer_pid = read_file(f'/var/run/{interface}.pid') - -openlog(ident=f'pppd[{dialer_pid}]', facility=LOG_INFO) -syslog('executing ' + argv[0]) conf = ConfigTreeQuery() -pppoe = conf.get_config_dict(['interfaces', 'pppoe', argv[6]], - get_first_key=True, key_mangling=('-', '_')) -pppoe['ifname'] = argv[6] +_, pppoe = get_interface_dict(conf.config, ['interfaces', 'pppoe'], interface) -p = PPPoEIf(pppoe['ifname']) +# Update the config +p = PPPoEIf(interface) p.update(pppoe) diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 new file mode 100755 index 000000000..a8936235e --- /dev/null +++ b/src/migration-scripts/interfaces/25-to-26 @@ -0,0 +1,54 @@ +#!/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/>. + +# T4384: pppoe: replace default-route CLI option with common CLI nodes already +# present for DHCP + +from sys import argv + +from vyos.ethtool import Ethtool +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 = ['interfaces', 'pppoe'] +config = ConfigTree(config_file) + +if not config.exists(base): + exit(0) + +for ifname in config.list_nodes(base): + tmp_config = base + [ifname, 'default-route'] + if config.exists(tmp_config): + # Retrieve current config value + value = config.return_value(tmp_config) + # Delete old Config node + config.delete(tmp_config) + if value == 'none': + config.set(base + [ifname, 'no-default-route']) + +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/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py index 29db41e37..0628e6135 100755 --- a/src/op_mode/generate_ovpn_client_file.py +++ b/src/op_mode/generate_ovpn_client_file.py @@ -18,6 +18,7 @@ import argparse import os from jinja2 import Template +from textwrap import fill from vyos.configquery import ConfigTreeQuery from vyos.ifconfig import Section @@ -117,8 +118,11 @@ if __name__ == '__main__': exit(f'OpenVPN certificate key "{key}" does not exist!') ca = config.value(['pki', 'ca', ca, 'certificate']) + ca = fill(ca, width=64) cert = config.value(['pki', 'certificate', cert, 'certificate']) + cert = fill(cert, width=64) key = config.value(['pki', 'certificate', key, 'private', 'key']) + key = fill(key, width=64) remote_host = config.value(base + [interface, 'local-host']) ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True) diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index e5014452f..91b25567a 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -22,6 +22,7 @@ import psutil from logging.handlers import SysLogHandler from shutil import rmtree +from vyos.base import Warning from vyos.util import call from vyos.util import ask_yes_no from vyos.util import process_named_running @@ -163,7 +164,7 @@ if cmd_args.action == 'restart': if cmd_args.daemon != ['']: for daemon in cmd_args.daemon: if not process_named_running(daemon): - print('WARNING: some of listed daemons are not running!') + Warning('some of listed daemons are not running!') # run command to restart daemon for daemon in cmd_args.daemon: diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py index cd6e8ed43..4b1758eea 100755 --- a/src/op_mode/show_dhcp.py +++ b/src/op_mode/show_dhcp.py @@ -26,6 +26,7 @@ from datetime import datetime from isc_dhcp_leases import Lease, IscDhcpLeases +from vyos.base import Warning from vyos.config import Config from vyos.util import is_systemd_service_running @@ -213,7 +214,7 @@ if __name__ == '__main__': # if dhcp server is down, inactive leases may still be shown as active, so warn the user. if not is_systemd_service_running('isc-dhcp-server.service'): - print("WARNING: DHCP server is configured but not started. Data may be stale.") + Warning('DHCP server is configured but not started. Data may be stale.') if args.leases: leases = get_leases(conf, lease_file, args.state, args.pool, args.sort) diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py index 1f987ff7b..b34b730e6 100755 --- a/src/op_mode/show_dhcpv6.py +++ b/src/op_mode/show_dhcpv6.py @@ -26,6 +26,7 @@ from datetime import datetime from isc_dhcp_leases import Lease, IscDhcpLeases +from vyos.base import Warning from vyos.config import Config from vyos.util import is_systemd_service_running @@ -203,7 +204,7 @@ if __name__ == '__main__': # if dhcp server is down, inactive leases may still be shown as active, so warn the user. if not is_systemd_service_running('isc-dhcp-server6.service'): - print("WARNING: DHCPv6 server is configured but not started. Data may be stale.") + Warning('DHCPv6 server is configured but not started. Data may be stale.') if args.leases: leases = get_leases(conf, lease_file, args.state, args.pool, args.sort) diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 40854fa8f..8955e5a59 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -88,7 +88,22 @@ def reset_profile(profile, tunnel): def debug_peer(peer, tunnel): if not peer or peer == "all": - call('sudo /usr/sbin/ipsec statusall') + debug_commands = [ + "sudo ipsec statusall", + "sudo swanctl -L", + "sudo swanctl -l", + "sudo swanctl -P", + "sudo ip x sa show", + "sudo ip x policy show", + "sudo ip tunnel show", + "sudo ip address", + "sudo ip rule show", + "sudo ip route | head -100", + "sudo ip route show table 220" + ] + for debug_cmd in debug_commands: + print(f'\n### {debug_cmd} ###') + call(debug_cmd) return if not tunnel or tunnel == 'all': diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index df9f18d2d..9ae7b1ea9 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -421,12 +421,12 @@ def pdns_rec_control(command): def make_resolv_conf(state): logger.info(f"Writing {RESOLV_CONF_FILE}") - render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.tmpl', state, + render(RESOLV_CONF_FILE, 'vyos-hostsd/resolv.conf.j2', state, user='root', group='root') def make_hosts(state): logger.info(f"Writing {HOSTS_FILE}") - render(HOSTS_FILE, 'vyos-hostsd/hosts.tmpl', state, + render(HOSTS_FILE, 'vyos-hostsd/hosts.j2', state, user='root', group='root') def make_pdns_rec_conf(state): @@ -437,12 +437,12 @@ def make_pdns_rec_conf(state): chmod_755(PDNS_REC_RUN_DIR) render(PDNS_REC_LUA_CONF_FILE, - 'dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl', + 'dns-forwarding/recursor.vyos-hostsd.conf.lua.j2', state, user=PDNS_REC_USER, group=PDNS_REC_GROUP) logger.info(f"Writing {PDNS_REC_ZONES_FILE}") render(PDNS_REC_ZONES_FILE, - 'dns-forwarding/recursor.forward-zones.conf.tmpl', + 'dns-forwarding/recursor.forward-zones.conf.j2', state, user=PDNS_REC_USER, group=PDNS_REC_GROUP) def set_host_name(state, data): |